1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-25 14:08:19 +01:00

(stable) Promote 2019 Week 10

This commit is contained in:
epriestley 2019-03-08 07:08:13 -08:00
commit 2387a99d71
104 changed files with 2717 additions and 896 deletions

View file

@ -9,9 +9,9 @@ return array(
'names' => array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'e3c1a8f2',
'core.pkg.css' => '34ce1741',
'core.pkg.js' => '2cda17a4',
'differential.pkg.css' => 'ab23bd75',
'differential.pkg.css' => '1755a478',
'differential.pkg.js' => '67e02996',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => '91192d85',
@ -61,7 +61,7 @@ return array(
'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6',
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
'rsrc/css/application/differential/changeset-view.css' => 'd92bed0d',
'rsrc/css/application/differential/changeset-view.css' => '4193eeff',
'rsrc/css/application/differential/core.css' => '7300a73e',
'rsrc/css/application/differential/phui-inline-comment.css' => '48acce5b',
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
@ -127,7 +127,7 @@ return array(
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '9e037c7a',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '534f1757',
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
@ -540,7 +540,7 @@ return array(
'conpherence-thread-manager' => 'aec8e38c',
'conpherence-transaction-css' => '3a3f5e7e',
'd3' => 'd67475f5',
'differential-changeset-view-css' => 'd92bed0d',
'differential-changeset-view-css' => '4193eeff',
'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d',
@ -834,7 +834,7 @@ return array(
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '470b1adb',
'phui-object-box-css' => 'f434b6be',
'phui-oi-big-ui-css' => '9e037c7a',
'phui-oi-big-ui-css' => '534f1757',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
'phui-oi-flush-ui-css' => '490e2e2e',
@ -1220,6 +1220,9 @@ return array(
'javelin-behavior',
'javelin-uri',
),
'4193eeff' => array(
'phui-inline-comment-view-css',
),
'4234f572' => array(
'syntax-default-css',
),
@ -1345,6 +1348,9 @@ return array(
'javelin-dom',
'javelin-fx',
),
'534f1757' => array(
'phui-oi-list-view-css',
),
'541f81c3' => array(
'javelin-install',
),
@ -1721,9 +1727,6 @@ return array(
'javelin-uri',
'phabricator-textareautils',
),
'9e037c7a' => array(
'phui-oi-list-view-css',
),
'9f081f05' => array(
'javelin-behavior',
'javelin-dom',
@ -1997,9 +2000,6 @@ return array(
'javelin-util',
'phabricator-shaped-request',
),
'd92bed0d' => array(
'phui-inline-comment-view-css',
),
'da15d3dc' => array(
'phui-oi-list-view-css',
),

View file

@ -1,11 +1,3 @@
<?php
$table = new PhabricatorOwnersPackage();
foreach (new LiskMigrationIterator($table) as $package) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$package->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new AlmanacDevice();
foreach (new LiskMigrationIterator($table) as $device) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$device->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new AlmanacService();
foreach (new LiskMigrationIterator($table) as $service) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$service->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new AlmanacNetwork();
foreach (new LiskMigrationIterator($table) as $network) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$network->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new HarbormasterBuildPlan();
foreach (new LiskMigrationIterator($table) as $plan) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$plan->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new DrydockBlueprint();
foreach (new LiskMigrationIterator($table) as $blueprint) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$blueprint->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new NuanceSource();
foreach (new LiskMigrationIterator($table) as $source) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$source->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new PhabricatorBadgesBadge();
foreach (new LiskMigrationIterator($table) as $badge) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$badge->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new PhabricatorPhurlURL();
foreach (new LiskMigrationIterator($table) as $url) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$url->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new ConpherenceThread();
foreach (new LiskMigrationIterator($table) as $thread) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$thread->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,21 +1,3 @@
<?php
$table_db = new PhabricatorDashboard();
foreach (new LiskMigrationIterator($table_db) as $dashboard) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$dashboard->getPHID(),
array(
'force' => true,
));
}
$table_dbp = new PhabricatorDashboardPanel();
foreach (new LiskMigrationIterator($table_dbp) as $panel) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$panel->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new PhabricatorProject();
foreach (new LiskMigrationIterator($table) as $project) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$project->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -1,11 +1,3 @@
<?php
$table = new PonderQuestion();
foreach (new LiskMigrationIterator($table) as $question) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$question->getPHID(),
array(
'force' => true,
));
}
// This was an old reindexing migration that has been obsoleted. See T13253.

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_harbormaster.harbormaster_buildplan
SET properties = '{}' WHERE properties = '';

View file

@ -0,0 +1 @@
DROP TABLE {$NAMESPACE}_herald.herald_ruletransaction_comment;

View file

@ -653,6 +653,7 @@ phutil_register_library_map(array(
'DifferentialRevisionUpdateTransaction' => 'applications/differential/xaction/DifferentialRevisionUpdateTransaction.php',
'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php',
'DifferentialRevisionVoidTransaction' => 'applications/differential/xaction/DifferentialRevisionVoidTransaction.php',
'DifferentialRevisionWrongBuildsTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongBuildsTransaction.php',
'DifferentialRevisionWrongStateTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php',
'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php',
'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php',
@ -1328,18 +1329,26 @@ phutil_register_library_map(array(
'HarbormasterBuildMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildMessageQuery.php',
'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
'HarbormasterBuildPlanBehavior' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php',
'HarbormasterBuildPlanBehaviorOption' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehaviorOption.php',
'HarbormasterBuildPlanBehaviorTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanBehaviorTransaction.php',
'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php',
'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php',
'HarbormasterBuildPlanEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanEditAPIMethod.php',
'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php',
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php',
'HarbormasterBuildPlanNameTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanNameTransaction.php',
'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
'HarbormasterBuildPlanPolicyCodex' => 'applications/harbormaster/codex/HarbormasterBuildPlanPolicyCodex.php',
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
'HarbormasterBuildPlanSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php',
'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
'HarbormasterBuildPlanStatusTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanStatusTransaction.php',
'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
'HarbormasterBuildPlanTransactionType' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanTransactionType.php',
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php',
'HarbormasterBuildSearchConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildSearchConduitAPIMethod.php',
@ -1366,6 +1375,7 @@ phutil_register_library_map(array(
'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php',
'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php',
'HarbormasterBuildUnitMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php',
'HarbormasterBuildView' => 'applications/harbormaster/view/HarbormasterBuildView.php',
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
@ -1419,6 +1429,7 @@ phutil_register_library_map(array(
'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php',
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php',
'HarbormasterPlanBehaviorController' => 'applications/harbormaster/controller/HarbormasterPlanBehaviorController.php',
'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php',
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
@ -1432,6 +1443,7 @@ phutil_register_library_map(array(
'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'applications/harbormaster/engineextension/HarbormasterQueryBuildsSearchEngineAttachment.php',
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
'HarbormasterRestartException' => 'applications/harbormaster/exception/HarbormasterRestartException.php',
'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php',
'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php',
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
@ -1520,14 +1532,20 @@ phutil_register_library_map(array(
'HeraldRemarkupFieldValue' => 'applications/herald/value/HeraldRemarkupFieldValue.php',
'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php',
'HeraldRule' => 'applications/herald/storage/HeraldRule.php',
'HeraldRuleActionAffectsObjectEdgeType' => 'applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php',
'HeraldRuleAdapter' => 'applications/herald/adapter/HeraldRuleAdapter.php',
'HeraldRuleAdapterField' => 'applications/herald/field/rule/HeraldRuleAdapterField.php',
'HeraldRuleController' => 'applications/herald/controller/HeraldRuleController.php',
'HeraldRuleDatasource' => 'applications/herald/typeahead/HeraldRuleDatasource.php',
'HeraldRuleDisableTransaction' => 'applications/herald/xaction/HeraldRuleDisableTransaction.php',
'HeraldRuleEditTransaction' => 'applications/herald/xaction/HeraldRuleEditTransaction.php',
'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php',
'HeraldRuleField' => 'applications/herald/field/rule/HeraldRuleField.php',
'HeraldRuleFieldGroup' => 'applications/herald/field/rule/HeraldRuleFieldGroup.php',
'HeraldRuleIndexEngineExtension' => 'applications/herald/engineextension/HeraldRuleIndexEngineExtension.php',
'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php',
'HeraldRuleListView' => 'applications/herald/view/HeraldRuleListView.php',
'HeraldRuleNameTransaction' => 'applications/herald/xaction/HeraldRuleNameTransaction.php',
'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php',
'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php',
'HeraldRuleReplyHandler' => 'applications/herald/mail/HeraldRuleReplyHandler.php',
@ -1535,7 +1553,7 @@ phutil_register_library_map(array(
'HeraldRuleSerializer' => 'applications/herald/editor/HeraldRuleSerializer.php',
'HeraldRuleTestCase' => 'applications/herald/storage/__tests__/HeraldRuleTestCase.php',
'HeraldRuleTransaction' => 'applications/herald/storage/HeraldRuleTransaction.php',
'HeraldRuleTransactionComment' => 'applications/herald/storage/HeraldRuleTransactionComment.php',
'HeraldRuleTransactionType' => 'applications/herald/xaction/HeraldRuleTransactionType.php',
'HeraldRuleTranscript' => 'applications/herald/storage/transcript/HeraldRuleTranscript.php',
'HeraldRuleTypeConfig' => 'applications/herald/config/HeraldRuleTypeConfig.php',
'HeraldRuleTypeDatasource' => 'applications/herald/typeahead/HeraldRuleTypeDatasource.php',
@ -1770,6 +1788,7 @@ phutil_register_library_map(array(
'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php',
'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php',
'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php',
'ManiphestTaskUnlockEngine' => 'applications/maniphest/engine/ManiphestTaskUnlockEngine.php',
'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php',
'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php',
'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php',
@ -2960,6 +2979,7 @@ phutil_register_library_map(array(
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php',
'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php',
'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php',
'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php',
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
@ -4687,6 +4707,8 @@ phutil_register_library_map(array(
'PhabricatorUnitTestContentSource' => 'infrastructure/contentsource/PhabricatorUnitTestContentSource.php',
'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php',
'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
'PhabricatorUnlockEngine' => 'applications/system/engine/PhabricatorUnlockEngine.php',
'PhabricatorUnlockableInterface' => 'applications/system/interface/PhabricatorUnlockableInterface.php',
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
'PhabricatorUserApproveTransaction' => 'applications/people/xaction/PhabricatorUserApproveTransaction.php',
@ -6171,6 +6193,7 @@ phutil_register_library_map(array(
'DifferentialRevisionUpdateTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionViewController' => 'DifferentialController',
'DifferentialRevisionVoidTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionWrongBuildsTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionWrongStateTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod',
@ -6936,19 +6959,28 @@ phutil_register_library_map(array(
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
'PhabricatorProjectInterface',
'PhabricatorPolicyCodexInterface',
),
'HarbormasterBuildPlanBehavior' => 'Phobject',
'HarbormasterBuildPlanBehaviorOption' => 'Phobject',
'HarbormasterBuildPlanBehaviorTransaction' => 'HarbormasterBuildPlanTransactionType',
'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability',
'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability',
'HarbormasterBuildPlanEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams',
'HarbormasterBuildPlanNameTransaction' => 'HarbormasterBuildPlanTransactionType',
'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildPlanPolicyCodex' => 'PhabricatorPolicyCodex',
'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildPlanSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction',
'HarbormasterBuildPlanStatusTransaction' => 'HarbormasterBuildPlanTransactionType',
'HarbormasterBuildPlanTransaction' => 'PhabricatorModularTransaction',
'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildPlanTransactionType' => 'PhabricatorModularTransactionType',
'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildRequest' => 'Phobject',
'HarbormasterBuildSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
@ -6991,6 +7023,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
),
'HarbormasterBuildUnitMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildView' => 'AphrontView',
'HarbormasterBuildViewController' => 'HarbormasterController',
'HarbormasterBuildWorker' => 'HarbormasterWorker',
'HarbormasterBuildable' => array(
@ -7047,6 +7080,7 @@ phutil_register_library_map(array(
'HarbormasterMessageType' => 'Phobject',
'HarbormasterObject' => 'HarbormasterDAO',
'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterPlanBehaviorController' => 'HarbormasterPlanController',
'HarbormasterPlanController' => 'HarbormasterController',
'HarbormasterPlanDisableController' => 'HarbormasterPlanController',
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
@ -7060,6 +7094,7 @@ phutil_register_library_map(array(
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'HarbormasterRestartException' => 'Exception',
'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction',
'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HarbormasterScratchTable' => 'HarbormasterDAO',
@ -7159,24 +7194,31 @@ phutil_register_library_map(array(
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorIndexableInterface',
'PhabricatorSubscribableInterface',
),
'HeraldRuleActionAffectsObjectEdgeType' => 'PhabricatorEdgeType',
'HeraldRuleAdapter' => 'HeraldAdapter',
'HeraldRuleAdapterField' => 'HeraldRuleField',
'HeraldRuleController' => 'HeraldController',
'HeraldRuleDatasource' => 'PhabricatorTypeaheadDatasource',
'HeraldRuleDisableTransaction' => 'HeraldRuleTransactionType',
'HeraldRuleEditTransaction' => 'HeraldRuleTransactionType',
'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
'HeraldRuleField' => 'HeraldField',
'HeraldRuleFieldGroup' => 'HeraldFieldGroup',
'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'HeraldRuleListController' => 'HeraldController',
'HeraldRuleListView' => 'AphrontView',
'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType',
'HeraldRulePHIDType' => 'PhabricatorPHIDType',
'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HeraldRuleReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'HeraldRuleSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HeraldRuleSerializer' => 'Phobject',
'HeraldRuleTestCase' => 'PhabricatorTestCase',
'HeraldRuleTransaction' => 'PhabricatorApplicationTransaction',
'HeraldRuleTransactionComment' => 'PhabricatorApplicationTransactionComment',
'HeraldRuleTransaction' => 'PhabricatorModularTransaction',
'HeraldRuleTransactionType' => 'PhabricatorModularTransactionType',
'HeraldRuleTranscript' => 'Phobject',
'HeraldRuleTypeConfig' => 'Phobject',
'HeraldRuleTypeDatasource' => 'PhabricatorTypeaheadDatasource',
@ -7388,6 +7430,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineLockableInterface',
'PhabricatorEditEngineMFAInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorUnlockableInterface',
),
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
@ -7465,6 +7508,7 @@ phutil_register_library_map(array(
'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType',
'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskUnlockEngine' => 'PhabricatorUnlockEngine',
'ManiphestTransaction' => 'PhabricatorModularTransaction',
'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment',
'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
@ -8842,6 +8886,7 @@ phutil_register_library_map(array(
'PhabricatorDebugController' => 'PhabricatorController',
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle',
'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine',
'PhabricatorDestructibleCodex' => 'Phobject',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDestructionEngineExtension' => 'Phobject',
@ -10852,6 +10897,7 @@ phutil_register_library_map(array(
'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource',
'PhabricatorUnitsTestCase' => 'PhabricatorTestCase',
'PhabricatorUnknownContentSource' => 'PhabricatorContentSource',
'PhabricatorUnlockEngine' => 'Phobject',
'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorUser' => array(
'PhabricatorUserDAO',

View file

@ -591,15 +591,11 @@ final class AphrontRequest extends Phobject {
}
public function getRequestURI() {
$request_uri = idx($_SERVER, 'REQUEST_URI', '/');
$uri_path = phutil_escape_uri($this->getPath());
$uri_query = idx($_SERVER, 'QUERY_STRING', '');
$uri = new PhutilURI($request_uri);
$uri->removeQueryParam('__path__');
$path = phutil_escape_uri($this->getPath());
$uri->setPath($path);
return $uri;
return id(new PhutilURI($uri_path.'?'.$uri_query))
->removeQueryParam('__path__');
}
public function getAbsoluteRequestURI() {

View file

@ -776,7 +776,6 @@ final class AphrontApplicationConfiguration
'filler' => str_repeat('Q', 1024 * 16),
);
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($result);

View file

@ -147,7 +147,7 @@ final class PhabricatorAuthInviteEngine extends Phobject {
// no address. Users can use password recovery to access the other
// account if they really control the address.
throw id(new PhabricatorAuthInviteAccountException(
pht('Wrong Acount'),
pht('Wrong Account'),
pht(
'You are logged in as %s, but the email address you just '.
'clicked a link from is already the primary email address '.

View file

@ -714,7 +714,14 @@ final class PhabricatorAuthSessionEngine extends Phobject {
if (isset($validation_results[$factor_phid])) {
continue;
}
$validation_results[$factor_phid] = new PhabricatorAuthFactorResult();
$issued_challenges = idx($challenge_map, $factor_phid, array());
$validation_results[$factor_phid] = $impl->getResultForPrompt(
$factor,
$viewer,
$request,
$issued_challenges);
}
throw id(new PhabricatorAuthHighSecurityRequiredException())

View file

@ -221,6 +221,40 @@ abstract class PhabricatorAuthFactor extends Phobject {
return $result;
}
final public function getResultForPrompt(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
assert_instances_of($challenges, 'PhabricatorAuthChallenge');
$result = $this->newResultForPrompt(
$config,
$viewer,
$request,
$challenges);
if (!$this->isAuthResult($result)) {
throw new Exception(
pht(
'Expected "newResultForPrompt()" to return an object of class "%s", '.
'but it returned something else ("%s"; in "%s").',
'PhabricatorAuthFactorResult',
phutil_describe_type($result),
get_class($this)));
}
return $result;
}
protected function newResultForPrompt(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
return $this->newResult();
}
abstract protected function newResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,

View file

@ -681,6 +681,19 @@ final class PhabricatorDuoAuthFactor
AphrontRequest $request,
array $challenges) {
return $this->getResultForPrompt(
$config,
$viewer,
$request,
$challenges);
}
protected function newResultForPrompt(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
$result = $this->newResult()
->setIsContinue(true)
->setErrorMessage(

View file

@ -58,6 +58,10 @@ final class PhabricatorConduitCallManagementWorkflow
'No such user "%s" exists.',
$as));
}
// Allow inline generation of user caches for the user we're acting
// as, since some calls may read user preferences.
$actor->setAllowInlineCacheGeneration(true);
} else {
$actor = $viewer;
}

View file

@ -409,4 +409,19 @@ abstract class ConduitAPIMethod
$capability);
}
final protected function newRemarkupDocumentationView($remarkup) {
$viewer = $this->getViewer();
$view = new PHUIRemarkupView($viewer, $remarkup);
$view->setRemarkupOptions(
array(
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false,
));
return id(new PHUIBoxView())
->appendChild($view)
->addPadding(PHUI::PADDING_LARGE);
}
}

View file

@ -129,30 +129,16 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
}
$structure = null;
$caught = null;
$extra_whitespace = ($body !== trim($body));
if (!$extra_whitespace) {
try {
$structure = phutil_json_decode($body);
} catch (Exception $ex) {
$caught = $ex;
}
try {
$structure = phutil_json_decode(trim($body));
} catch (Exception $ex) {
// Ignore the exception, we only care if the decode worked or not.
}
if (!$structure) {
if ($extra_whitespace) {
$message = pht(
'Phabricator sent itself a test request and expected to get a bare '.
'JSON response back, but the response had extra whitespace at '.
'the beginning or end.'.
"\n\n".
'This usually means you have edited a file and left whitespace '.
'characters before the opening %s tag, or after a closing %s tag. '.
'Remove any leading whitespace, and prefer to omit closing tags.',
phutil_tag('tt', array(), '<?php'),
phutil_tag('tt', array(), '?>'));
} else {
if (!$structure || $extra_whitespace) {
if (!$structure) {
$short = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(1024)
->truncateString($body);
@ -166,6 +152,17 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
"\n\n".
'Something is misconfigured or otherwise mangling responses.',
phutil_tag('pre', array(), $short));
} else {
$message = pht(
'Phabricator sent itself a test request and expected to get a bare '.
'JSON response back. It received a JSON response, but the response '.
'had extra whitespace at the beginning or end.'.
"\n\n".
'This usually means you have edited a file and left whitespace '.
'characters before the opening %s tag, or after a closing %s tag. '.
'Remove any leading whitespace, and prefer to omit closing tags.',
phutil_tag('tt', array(), '<?php'),
phutil_tag('tt', array(), '?>'));
}
$this->newIssue('webserver.mangle')
@ -174,7 +171,9 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
->setMessage($message);
// We can't run the other checks if we could not decode the response.
return;
if (!$structure) {
return;
}
}
$actual_user = idx($structure, 'user');

View file

@ -43,7 +43,7 @@ final class PhabricatorSetConfigType
}
if ($value) {
if (array_keys($value) !== range(0, count($value) - 1)) {
if (!phutil_is_natural_list($value)) {
throw $this->newException(
pht(
'Option "%s" is of type "%s", and should be specified on the '.

View file

@ -204,9 +204,9 @@ final class DifferentialInlineCommentEditController
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %Q',
'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %LQ',
$table->getTableName(),
implode(', ', $sql));
$sql);
}
protected function showComments(array $ids) {

View file

@ -208,15 +208,6 @@ final class DifferentialDiffEditor
return $adapter;
}
protected function didApplyHeraldRules(
PhabricatorLiskDAO $object,
HeraldAdapter $adapter,
HeraldTranscript $transcript) {
$xactions = array();
return $xactions;
}
private function updateDiffFromDict(DifferentialDiff $diff, $dict) {
$diff
->setSourcePath(idx($dict, 'sourcePath'))

View file

@ -54,6 +54,12 @@ final class DifferentialChangesetEngine extends Phobject {
if (strpos($new_data, '@'.'generated') !== false) {
return true;
}
// See PHI1112. This is the official pattern for marking Go code as
// generated.
if (preg_match('(^// Code generated .* DO NOT EDIT\.$)m', $new_data)) {
return true;
}
}
return false;

View file

@ -285,6 +285,24 @@ final class DifferentialDiffExtractionEngine extends Phobject {
->setNewValue($revision->getModernRevisionStatus());
}
$concerning_builds = $this->loadConcerningBuilds($revision);
if ($concerning_builds) {
$build_list = array();
foreach ($concerning_builds as $build) {
$build_list[] = array(
'phid' => $build->getPHID(),
'status' => $build->getBuildStatus(),
);
}
$wrong_builds =
DifferentialRevisionWrongBuildsTransaction::TRANSACTIONTYPE;
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($wrong_builds)
->setNewValue($build_list);
}
$type_update = DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE;
$xactions[] = id(new DifferentialTransaction())
@ -322,4 +340,81 @@ final class DifferentialDiffExtractionEngine extends Phobject {
return $result_data;
}
private function loadConcerningBuilds(DifferentialRevision $revision) {
$viewer = $this->getViewer();
$diff = $revision->getActiveDiff();
$buildables = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withBuildablePHIDs(array($diff->getPHID()))
->needBuilds(true)
->withManualBuildables(false)
->execute();
if (!$buildables) {
return array();
}
$land_key = HarbormasterBuildPlanBehavior::BEHAVIOR_LANDWARNING;
$behavior = HarbormasterBuildPlanBehavior::getBehavior($land_key);
$key_never = HarbormasterBuildPlanBehavior::LANDWARNING_NEVER;
$key_building = HarbormasterBuildPlanBehavior::LANDWARNING_IF_BUILDING;
$key_complete = HarbormasterBuildPlanBehavior::LANDWARNING_IF_COMPLETE;
$concerning_builds = array();
foreach ($buildables as $buildable) {
$builds = $buildable->getBuilds();
foreach ($builds as $build) {
$plan = $build->getBuildPlan();
$option = $behavior->getPlanOption($plan);
$behavior_value = $option->getKey();
$if_never = ($behavior_value === $key_never);
if ($if_never) {
continue;
}
$if_building = ($behavior_value === $key_building);
if ($if_building && $build->isComplete()) {
continue;
}
$if_complete = ($behavior_value === $key_complete);
if ($if_complete) {
if (!$build->isComplete()) {
continue;
}
// TODO: If you "arc land" and a build with "Warn: If Complete"
// is still running, you may not see a warning, and push the revision
// in good faith. The build may then complete before we get here, so
// we now see a completed, failed build.
// For now, just err on the side of caution and assume these builds
// were in a good state when we prompted the user, even if they're in
// a bad state now.
// We could refine this with a rule like "if the build finished
// within a couple of minutes before the push happened, assume it was
// in good faith", but we don't currently have an especially
// convenient way to check when the build finished or when the commit
// was pushed or discovered, and this would create some issues in
// cases where the repository is observed and the fetch pipeline
// stalls for a while.
continue;
}
if ($build->isPassed()) {
continue;
}
$concerning_builds[] = $build;
}
}
return $concerning_builds;
}
}

View file

@ -162,7 +162,20 @@ final class DifferentialChangesetTwoUpRenderer
} else if (empty($new_lines[$ii])) {
$o_class = 'old old-full';
} else {
$o_class = 'old';
if (isset($depth_only[$ii])) {
if ($depth_only[$ii] == '>') {
// When a line has depth-only change, we only highlight the
// left side of the diff if the depth is decreasing. When the
// depth is increasing, the ">>" marker on the right hand side
// of the diff generally provides enough visibility on its own.
$o_class = '';
} else {
$o_class = 'old';
}
} else {
$o_class = 'old';
}
}
$o_classes = $o_class;
}
@ -200,13 +213,10 @@ final class DifferentialChangesetTwoUpRenderer
} else if (empty($old_lines[$ii])) {
$n_class = 'new new-full';
} else {
// NOTE: At least for the moment, I'm intentionally clearing the
// line highlighting only on the right side of the diff when a
// line has only depth changes. When a block depth is decreased,
// this gives us a large color block on the left (to make it easy
// to see the depth change) but a clean diff on the right (to make
// it easy to pick out actual code changes).
// When a line has a depth-only change, never highlight it on
// the right side. The ">>" marker generally provides enough
// visibility on its own for indent depth increases, and the left
// side is still highlighted for indent depth decreases.
if (isset($depth_only[$ii])) {
$n_class = '';

View file

@ -877,7 +877,7 @@ final class DifferentialRevision extends DifferentialDAO
PhabricatorUser $viewer,
array $phids) {
return id(new HarbormasterBuildQuery())
$builds = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withBuildablePHIDs($phids)
->withAutobuilds(false)
@ -893,6 +893,41 @@ final class DifferentialRevision extends DifferentialDAO
HarbormasterBuildStatus::STATUS_DEADLOCKED,
))
->execute();
// Filter builds based on the "Hold Drafts" behavior of their associated
// build plans.
$hold_drafts = HarbormasterBuildPlanBehavior::BEHAVIOR_DRAFTS;
$behavior = HarbormasterBuildPlanBehavior::getBehavior($hold_drafts);
$key_never = HarbormasterBuildPlanBehavior::DRAFTS_NEVER;
$key_building = HarbormasterBuildPlanBehavior::DRAFTS_IF_BUILDING;
foreach ($builds as $key => $build) {
$plan = $build->getBuildPlan();
$hold_key = $behavior->getPlanOption($plan)->getKey();
$hold_never = ($hold_key === $key_never);
$hold_building = ($hold_key === $key_building);
// If the build "Never" holds drafts from promoting, we don't care what
// the status is.
if ($hold_never) {
unset($builds[$key]);
continue;
}
// If the build holds drafts from promoting "While Building", we only
// care about the status until it completes.
if ($hold_building) {
if ($build->isComplete()) {
unset($builds[$key]);
continue;
}
}
}
return $builds;
}

View file

@ -0,0 +1,37 @@
<?php
final class DifferentialRevisionWrongBuildsTransaction
extends DifferentialRevisionTransactionType {
const TRANSACTIONTYPE = 'differential.builds.wrong';
public function generateOldValue($object) {
return null;
}
public function generateNewValue($object, $value) {
return $value;
}
public function getIcon() {
return 'fa-exclamation';
}
public function getColor() {
return 'pink';
}
public function getActionStrength() {
return 4;
}
public function getTitle() {
return pht(
'This revision was landed with ongoing or failed builds.');
}
public function shouldHideForFeed() {
return true;
}
}

View file

@ -368,9 +368,9 @@ final class DiffusionBrowseQueryConduitAPIMethod
}
if ($commit) {
$slice_clause = 'AND svnCommit <= '.(int)$commit;
$slice_clause = qsprintf($conn_r, 'AND svnCommit <= %d', $commit);
} else {
$slice_clause = '';
$slice_clause = qsprintf($conn_r, '');
}
$index = queryfx_all(
@ -439,9 +439,11 @@ final class DiffusionBrowseQueryConduitAPIMethod
$sql = array();
foreach ($index as $row) {
$sql[] =
'(pathID = '.(int)$row['pathID'].' AND '.
'svnCommit = '.(int)$row['maxCommit'].')';
$sql[] = qsprintf(
$conn_r,
'(pathID = %d AND svnCommit = %d)',
$row['pathID'],
$row['maxCommit']);
}
$browse = queryfx_all(

View file

@ -215,13 +215,17 @@ final class DiffusionHistoryQueryConduitAPIMethod
return array();
}
$filter_query = '';
$filter_query = qsprintf($conn_r, '');
if ($need_direct_changes) {
if ($need_child_changes) {
$type = DifferentialChangeType::TYPE_CHILD;
$filter_query = 'AND (isDirect = 1 OR changeType = '.$type.')';
$filter_query = qsprintf(
$conn_r,
'AND (isDirect = 1 OR changeType = %s)',
DifferentialChangeType::TYPE_CHILD);
} else {
$filter_query = 'AND (isDirect = 1)';
$filter_query = qsprintf(
$conn_r,
'AND (isDirect = 1)');
}
}

View file

@ -709,8 +709,17 @@ final class DiffusionBrowseController extends DiffusionController {
'path' => $path,
));
$before_uri = $before_uri->alter('renamed', $renamed);
$before_uri = $before_uri->alter('follow', $follow);
if ($renamed === null) {
$before_uri->removeQueryParam('renamed');
} else {
$before_uri->replaceQueryParam('renamed', $renamed);
}
if ($follow === null) {
$before_uri->removeQueryParam('follow');
} else {
$before_uri->replaceQueryParam('follow', $follow);
}
return id(new AphrontRedirectResponse())->setURI($before_uri);
}

View file

@ -192,7 +192,10 @@ final class DiffusionServeController extends DiffusionController {
// Try Git LFS auth first since we can usually reject it without doing
// any queries, since the username won't match the one we expect or the
// request won't be LFS.
$viewer = $this->authenticateGitLFSUser($username, $password);
$viewer = $this->authenticateGitLFSUser(
$username,
$password,
$identifier);
// If that failed, try normal auth. Note that we can use normal auth on
// LFS requests, so this isn't strictly an alternative to LFS auth.
@ -655,7 +658,8 @@ final class DiffusionServeController extends DiffusionController {
private function authenticateGitLFSUser(
$username,
PhutilOpaqueEnvelope $password) {
PhutilOpaqueEnvelope $password,
$identifier) {
// Never accept these credentials for requests which aren't LFS requests.
if (!$this->getIsGitLFSRequest()) {
@ -668,11 +672,31 @@ final class DiffusionServeController extends DiffusionController {
return null;
}
// See PHI1123. We need to be able to constrain the token query with
// "withTokenResources(...)" to take advantage of the key on the table.
// In this case, the repository PHID is the "resource" we're after.
// In normal workflows, we figure out the viewer first, then use the
// viewer to load the repository, but that won't work here. Load the
// repository as the omnipotent viewer, then use the repository PHID to
// look for a token.
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($omnipotent_viewer)
->withIdentifiers(array($identifier))
->executeOne();
if (!$repository) {
return null;
}
$lfs_pass = $password->openEnvelope();
$lfs_hash = PhabricatorHash::weakDigest($lfs_pass);
$token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setViewer($omnipotent_viewer)
->withTokenResources(array($repository->getPHID()))
->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE))
->withTokenCodes(array($lfs_hash))
->withExpired(false)
@ -682,7 +706,7 @@ final class DiffusionServeController extends DiffusionController {
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setViewer($omnipotent_viewer)
->withPHIDs(array($token->getUserPHID()))
->executeOne();

View file

@ -444,13 +444,15 @@ final class DiffusionRepositoryBasicsManagementPanel
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(
pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))
->setNote(pht(
'Unable to find this binary in `%s`. '.
'You need to configure %s and include %s.',
'environment.append-paths',
$this->getEnvConfigLink(),
$path)));
pht('Commit Hooks: %s', phutil_tag('tt', array(), $binary)))
->setNote(
pht(
'The directory containing the "svnlook" binary is not '.
'listed in "environment.append-paths", so commit hooks '.
'(which execute with an empty "PATH") will not be able to '.
'find "svnlook". Add `%s` to %s.',
$path,
$this->getEnvConfigLink())));
}
}
}

View file

@ -127,9 +127,9 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
// This is suppressing "added <address> to the list of known hosts"
// messages, which are confusing and irrelevant when they arise from
// proxied requests. It might also be suppressing lots of useful errors,
// of course. Ideally, we would enforce host keys eventually.
// of course. Ideally, we would enforce host keys eventually. See T13121.
$options[] = '-o';
$options[] = 'LogLevel=quiet';
$options[] = 'LogLevel=ERROR';
// NOTE: We prefix the command with "@username", which the far end of the
// connection will parse in order to act as the specified user. This

View file

@ -30,8 +30,11 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface {
$full_command = call_user_func_array('csprintf', $argv);
$flags = array();
// See T13121. Attempt to suppress the "Permanently added X to list of
// known hosts" message without suppressing anything important.
$flags[] = '-o';
$flags[] = 'LogLevel=quiet';
$flags[] = 'LogLevel=ERROR';
$flags[] = '-o';
$flags[] = 'StrictHostKeyChecking=no';

View file

@ -83,6 +83,8 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
=> 'HarbormasterPlanEditController',
'order/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanOrderController',
'disable/(?P<id>\d+)/' => 'HarbormasterPlanDisableController',
'behavior/(?P<id>\d+)/(?P<behaviorKey>[^/]+)/' =>
'HarbormasterPlanBehaviorController',
'run/(?P<id>\d+)/' => 'HarbormasterPlanRunController',
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
),

View file

@ -0,0 +1,38 @@
<?php
final class HarbormasterBuildPlanPolicyCodex
extends PhabricatorPolicyCodex {
public function getPolicySpecialRuleDescriptions() {
$object = $this->getObject();
$run_with_view = $object->canRunWithoutEditCapability();
$rules = array();
$rules[] = $this->newRule()
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
))
->setIsActive(!$run_with_view)
->setDescription(
pht(
'You must have edit permission on this build plan to pause, '.
'abort, resume, or restart it.'));
$rules[] = $this->newRule()
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
))
->setIsActive(!$run_with_view)
->setDescription(
pht(
'You must have edit permission on this build plan to run it '.
'manually.'));
return $rules;
}
}

View file

@ -0,0 +1,20 @@
<?php
final class HarbormasterBuildPlanEditAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'harbormaster.buildplan.edit';
}
public function newEditEngine() {
return new HarbormasterBuildPlanEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new build plan or edit an existing '.
'one.');
}
}

View file

@ -52,6 +52,10 @@ final class HarbormasterBuildStatus extends Phobject {
return ($this->key === self::STATUS_PASSED);
}
public function isFailed() {
return ($this->key === self::STATUS_FAILED);
}
/**
* Get a human readable name for a build status constant.

View file

@ -65,13 +65,12 @@ final class HarbormasterBuildActionController
'restart build?');
$submit = pht('Restart Build');
} else {
$title = pht('Unable to Restart Build');
if ($build->isRestarting()) {
$body = pht(
'This build is already restarting. You can not reissue a '.
'restart command to a restarting build.');
} else {
$body = pht('You can not restart this build.');
try {
$build->assertCanRestartBuild();
throw new Exception(pht('Expected to be unable to restart build.'));
} catch (HarbormasterRestartException $ex) {
$title = $ex->getTitle();
$body = $ex->getBody();
}
}
break;
@ -135,8 +134,7 @@ final class HarbormasterBuildActionController
break;
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
$dialog = $this->newDialog()
->setTitle($title)
->appendChild($body)
->addCancelButton($return_uri);
@ -145,7 +143,7 @@ final class HarbormasterBuildActionController
$dialog->addSubmitButton($submit);
}
return id(new AphrontDialogResponse())->setDialog($dialog);
return $dialog;
}
}

View file

@ -0,0 +1,92 @@
<?php
final class HarbormasterPlanBehaviorController
extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
$behavior_key = $request->getURIData('behaviorKey');
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
$behavior = idx($behaviors, $behavior_key);
if (!$behavior) {
return new Aphront404Response();
}
$plan_uri = $plan->getURI();
$v_option = $behavior->getPlanOption($plan)->getKey();
if ($request->isFormPost()) {
$v_option = $request->getStr('option');
$xactions = array();
$xactions[] = id(new HarbormasterBuildPlanTransaction())
->setTransactionType(
HarbormasterBuildPlanBehaviorTransaction::TRANSACTIONTYPE)
->setMetadataValue($metadata_key, $behavior_key)
->setNewValue($v_option);
$editor = id(new HarbormasterBuildPlanEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($plan, $xactions);
return id(new AphrontRedirectResponse())->setURI($plan_uri);
}
$select_control = id(new AphrontFormRadioButtonControl())
->setName('option')
->setValue($v_option)
->setLabel(pht('Option'));
foreach ($behavior->getOptions() as $option) {
$icon = id(new PHUIIconView())
->setIcon($option->getIcon());
$select_control->addButton(
$option->getKey(),
array(
$icon,
' ',
$option->getName(),
),
$option->getDescription());
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendInstructions(
pht(
'Choose a build plan behavior for "%s".',
phutil_tag('strong', array(), $behavior->getName())))
->appendRemarkupInstructions($behavior->getEditInstructions())
->appendControl($select_control);
return $this->newDialog()
->setTitle(pht('Edit Behavior: %s', $behavior->getName()))
->appendForm($form)
->setWidth(AphrontDialogView::WIDTH_FORM)
->addSubmitButton(pht('Save Changes'))
->addCancelButton($plan_uri);
}
}

View file

@ -19,11 +19,11 @@ final class HarbormasterPlanDisableController
return new Aphront404Response();
}
$plan_uri = $this->getApplicationURI('plan/'.$plan->getID().'/');
$plan_uri = $plan->getURI();
if ($request->isFormPost()) {
$type_status = HarbormasterBuildPlanTransaction::TYPE_STATUS;
$type_status = HarbormasterBuildPlanStatusTransaction::TRANSACTIONTYPE;
$v_status = $plan->isDisabled()
? HarbormasterBuildPlan::STATUS_ACTIVE

View file

@ -9,16 +9,13 @@ final class HarbormasterPlanRunController extends HarbormasterPlanController {
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($plan_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
$plan->assertHasRunCapability($viewer);
$cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
if (!$plan->canRunManually()) {

View file

@ -18,11 +18,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
return new Aphront404Response();
}
$timeline = $this->buildTransactionTimeline(
$plan,
new HarbormasterBuildPlanTransactionQuery());
$timeline->setShouldTerminate(true);
$title = $plan->getName();
$header = id(new PHUIHeaderView())
@ -33,24 +28,30 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
$curtain = $this->buildCurtainView($plan);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Plan %d', $id));
$crumbs->setBorder(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($plan->getObjectName())
->setBorder(true);
list($step_list, $has_any_conflicts, $would_deadlock) =
list($step_list, $has_any_conflicts, $would_deadlock, $steps) =
$this->buildStepList($plan);
$error = null;
if ($would_deadlock) {
$error = pht('This build plan will deadlock when executed, due to '.
'circular dependencies present in the build plan. '.
'Examine the step list and resolve the deadlock.');
if (!$steps) {
$error = pht(
'This build plan does not have any build steps yet, so it will '.
'not do anything when run.');
} else if ($would_deadlock) {
$error = pht(
'This build plan will deadlock when executed, due to circular '.
'dependencies present in the build plan. Examine the step list '.
'and resolve the deadlock.');
} else if ($has_any_conflicts) {
// A deadlocking build will also cause all the artifacts to be
// invalid, so we just skip showing this message if that's the
// case.
$error = pht('This build plan has conflicts in one or more build steps. '.
'Examine the step list and resolve the listed errors.');
$error = pht(
'This build plan has conflicts in one or more build steps. '.
'Examine the step list and resolve the listed errors.');
}
if ($error) {
@ -59,18 +60,32 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
->appendChild($error);
}
$builds_view = $this->newBuildsView($plan);
$options_view = $this->newOptionsView($plan);
$rules_view = $this->newRulesView($plan);
$timeline = $this->buildTransactionTimeline(
$plan,
new HarbormasterBuildPlanTransactionQuery());
$timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$error,
$step_list,
$timeline,
));
->setMainColumn(
array(
$error,
$step_list,
$options_view,
$rules_view,
$builds_view,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($plan->getPHID()))
->appendChild($view);
}
@ -213,7 +228,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($step_list);
return array($step_box, $has_any_conflicts, $is_deadlocking);
return array($step_box, $has_any_conflicts, $is_deadlocking, $steps);
}
private function buildCurtainView(HarbormasterBuildPlan $plan) {
@ -253,7 +268,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
->setIcon('fa-ban'));
}
$can_run = ($can_edit && $plan->canRunManually());
$can_run = ($plan->hasRunCapability($viewer) && $plan->canRunManually());
$curtain->addAction(
id(new PhabricatorActionView())
@ -263,11 +278,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
->setDisabled(!$can_run)
->setIcon('fa-play-circle'));
$curtain->addPanel(
id(new PHUICurtainPanelView())
->setHeaderText(pht('Created'))
->appendChild(phabricator_datetime($plan->getDateCreated(), $viewer)));
return $curtain;
}
@ -381,7 +391,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
array $steps) {
$has_conflicts = false;
if (count($step_phids) === 0) {
if (!$step_phids) {
return null;
}
@ -441,4 +451,149 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
return array($ui, $has_conflicts);
}
private function newBuildsView(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$builds = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withBuildPlanPHIDs(array($plan->getPHID()))
->setLimit(10)
->execute();
$list = id(new HarbormasterBuildView())
->setViewer($viewer)
->setBuilds($builds)
->newObjectList();
$list->setNoDataString(pht('No recent builds.'));
$more_href = new PhutilURI(
$this->getApplicationURI('/build/'),
array('plan' => $plan->getPHID()));
$more_link = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-list-ul')
->setText(pht('View All Builds'))
->setHref($more_href);
$header = id(new PHUIHeaderView())
->setHeader(pht('Recent Builds'))
->addActionLink($more_link);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
}
private function newRulesView(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$rules = id(new HeraldRuleQuery())
->setViewer($viewer)
->withDisabled(false)
->withAffectedObjectPHIDs(array($plan->getPHID()))
->needValidateAuthors(true)
->setLimit(10)
->execute();
$list = id(new HeraldRuleListView())
->setViewer($viewer)
->setRules($rules)
->newObjectList();
$list->setNoDataString(pht('No active Herald rules trigger this build.'));
$more_href = new PhutilURI(
'/herald/',
array('affectedPHID' => $plan->getPHID()));
$more_link = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-list-ul')
->setText(pht('View All Rules'))
->setHref($more_href);
$header = id(new PHUIHeaderView())
->setHeader(pht('Run By Herald Rules'))
->addActionLink($more_link);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
}
private function newOptionsView(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
$rows = array();
foreach ($behaviors as $behavior) {
$option = $behavior->getPlanOption($plan);
$icon = $option->getIcon();
$icon = id(new PHUIIconView())->setIcon($icon);
$edit_uri = new PhutilURI(
$this->getApplicationURI(
urisprintf(
'plan/behavior/%d/%s/',
$plan->getID(),
$behavior->getKey())));
$edit_button = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::GREY)
->setSize(PHUIButtonView::SMALL)
->setDisabled(!$can_edit)
->setWorkflow(true)
->setText(pht('Edit'))
->setHref($edit_uri);
$rows[] = array(
$icon,
$behavior->getName(),
$option->getName(),
$option->getDescription(),
$edit_button,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Name'),
pht('Behavior'),
pht('Details'),
null,
))
->setColumnClasses(
array(
null,
'pri',
null,
'wide',
null,
));
$header = id(new PHUIHeaderView())
->setHeader(pht('Plan Behaviors'));
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
}

View file

@ -77,17 +77,48 @@ final class HarbormasterBuildPlanEditEngine
}
protected function buildCustomEditFields($object) {
return array(
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setIsRequired(true)
->setTransactionType(HarbormasterBuildPlanTransaction::TYPE_NAME)
->setTransactionType(
HarbormasterBuildPlanNameTransaction::TRANSACTIONTYPE)
->setDescription(pht('The build plan name.'))
->setConduitDescription(pht('Rename the plan.'))
->setConduitTypeDescription(pht('New plan name.'))
->setValue($object->getName()),
);
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
foreach ($behaviors as $behavior) {
$key = $behavior->getKey();
// Get the raw key off the object so that we don't reset stuff to
// default values by mistake if a behavior goes missing somehow.
$storage_key = HarbormasterBuildPlanBehavior::getStorageKeyForBehaviorKey(
$key);
$behavior_option = $object->getPlanProperty($storage_key);
if (!strlen($behavior_option)) {
$behavior_option = $behavior->getPlanOption($object)->getKey();
}
$fields[] = id(new PhabricatorSelectEditField())
->setIsFormField(false)
->setKey(sprintf('behavior.%s', $behavior->getKey()))
->setMetadataValue($metadata_key, $behavior->getKey())
->setLabel(pht('Behavior: %s', $behavior->getName()))
->setTransactionType(
HarbormasterBuildPlanBehaviorTransaction::TRANSACTIONTYPE)
->setValue($behavior_option)
->setOptions($behavior->getOptionMap());
}
return $fields;
}
}

View file

@ -11,100 +11,23 @@ final class HarbormasterBuildPlanEditor
return pht('Harbormaster Build Plans');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this build plan.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
protected function supportsSearch() {
return true;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
$types[] = HarbormasterBuildPlanTransaction::TYPE_STATUS;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HarbormasterBuildPlanTransaction::TYPE_NAME:
if ($this->getIsNewObject()) {
return null;
}
return $object->getName();
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
return $object->getPlanStatus();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HarbormasterBuildPlanTransaction::TYPE_NAME:
return $xaction->getNewValue();
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HarbormasterBuildPlanTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
$object->setPlanStatus($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HarbormasterBuildPlanTransaction::TYPE_NAME:
case HarbormasterBuildPlanTransaction::TYPE_STATUS:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case HarbormasterBuildPlanTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('You must choose a name for your build plan.'),
last($xactions));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
}
return $errors;
}
}

View file

@ -497,9 +497,33 @@ final class HarbormasterBuildEngine extends Phobject {
// passed everything it needs to.
if (!$buildable->isPreparing()) {
$behavior_key = HarbormasterBuildPlanBehavior::BEHAVIOR_BUILDABLE;
$behavior = HarbormasterBuildPlanBehavior::getBehavior($behavior_key);
$key_never = HarbormasterBuildPlanBehavior::BUILDABLE_NEVER;
$key_building = HarbormasterBuildPlanBehavior::BUILDABLE_IF_BUILDING;
$all_pass = true;
$any_fail = false;
foreach ($buildable->getBuilds() as $build) {
$plan = $build->getBuildPlan();
$option = $behavior->getPlanOption($plan);
$option_key = $option->getKey();
$is_never = ($option_key === $key_never);
$is_building = ($option_key === $key_building);
// If this build "Never" affects the buildable, ignore it.
if ($is_never) {
continue;
}
// If this build affects the buildable "If Building", but is already
// complete, ignore it.
if ($is_building && $build->isComplete()) {
continue;
}
if (!$build->isPassed()) {
$all_pass = false;
}

View file

@ -0,0 +1,33 @@
<?php
final class HarbormasterRestartException extends Exception {
private $title;
private $body = array();
public function __construct($title, $body = null) {
$this->setTitle($title);
$this->appendParagraph($body);
parent::__construct($title);
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function appendParagraph($description) {
$this->body[] = $description;
return $this;
}
public function getBody() {
return $this->body;
}
}

View file

@ -91,4 +91,9 @@ final class HarbormasterRunBuildPlansHeraldAction
'Run build plans: %s.',
$this->renderHandleList($value));
}
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
return $record->getTarget();
}
}

View file

@ -0,0 +1,394 @@
<?php
final class HarbormasterBuildPlanBehavior
extends Phobject {
private $key;
private $name;
private $options;
private $defaultKey;
private $editInstructions;
const BEHAVIOR_RUNNABLE = 'runnable';
const RUNNABLE_IF_VIEWABLE = 'view';
const RUNNABLE_IF_EDITABLE = 'edit';
const BEHAVIOR_RESTARTABLE = 'restartable';
const RESTARTABLE_ALWAYS = 'always';
const RESTARTABLE_IF_FAILED = 'failed';
const RESTARTABLE_NEVER = 'never';
const BEHAVIOR_DRAFTS = 'hold-drafts';
const DRAFTS_ALWAYS = 'always';
const DRAFTS_IF_BUILDING = 'building';
const DRAFTS_NEVER = 'never';
const BEHAVIOR_BUILDABLE = 'buildable';
const BUILDABLE_ALWAYS = 'always';
const BUILDABLE_IF_BUILDING = 'building';
const BUILDABLE_NEVER = 'never';
const BEHAVIOR_LANDWARNING = 'arc-land';
const LANDWARNING_ALWAYS = 'always';
const LANDWARNING_IF_BUILDING = 'building';
const LANDWARNING_IF_COMPLETE = 'complete';
const LANDWARNING_NEVER = 'never';
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setEditInstructions($edit_instructions) {
$this->editInstructions = $edit_instructions;
return $this;
}
public function getEditInstructions() {
return $this->editInstructions;
}
public function getOptionMap() {
return mpull($this->options, 'getName', 'getKey');
}
public function setOptions(array $options) {
assert_instances_of($options, 'HarbormasterBuildPlanBehaviorOption');
$key_map = array();
$default = null;
foreach ($options as $option) {
$key = $option->getKey();
if (isset($key_map[$key])) {
throw new Exception(
pht(
'Multiple behavior options (for behavior "%s") have the same '.
'key ("%s"). Each option must have a unique key.',
$this->getKey(),
$key));
}
$key_map[$key] = true;
if ($option->getIsDefault()) {
if ($default === null) {
$default = $key;
} else {
throw new Exception(
pht(
'Multiple behavior options (for behavior "%s") are marked as '.
'default options ("%s" and "%s"). Exactly one option must be '.
'marked as the default option.',
$this->getKey(),
$default,
$key));
}
}
}
if ($default === null) {
throw new Exception(
pht(
'No behavior option is marked as the default option (for '.
'behavior "%s"). Exactly one option must be marked as the '.
'default option.',
$this->getKey()));
}
$this->options = mpull($options, null, 'getKey');
$this->defaultKey = $default;
return $this;
}
public function getOptions() {
return $this->options;
}
public function getPlanOption(HarbormasterBuildPlan $plan) {
$behavior_key = $this->getKey();
$storage_key = self::getStorageKeyForBehaviorKey($behavior_key);
$plan_value = $plan->getPlanProperty($storage_key);
if (isset($this->options[$plan_value])) {
return $this->options[$plan_value];
}
return idx($this->options, $this->defaultKey);
}
public static function getTransactionMetadataKey() {
return 'behavior-key';
}
public static function getStorageKeyForBehaviorKey($behavior_key) {
return sprintf('behavior.%s', $behavior_key);
}
public static function getBehavior($key) {
$behaviors = self::newPlanBehaviors();
if (!isset($behaviors[$key])) {
throw new Exception(
pht(
'No build plan behavior with key "%s" exists.',
$key));
}
return $behaviors[$key];
}
public static function newPlanBehaviors() {
$draft_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::DRAFTS_ALWAYS)
->setIcon('fa-check-circle-o green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht(
'Revisions are not sent for review until the build completes, '.
'and are returned to the author for updates if the build fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::DRAFTS_IF_BUILDING)
->setIcon('fa-pause-circle-o yellow')
->setName(pht('If Building'))
->setDescription(
pht(
'Revisions are not sent for review until the build completes, '.
'but they will be sent for review even if it fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::DRAFTS_NEVER)
->setIcon('fa-circle-o red')
->setName(pht('Never'))
->setDescription(
pht(
'Revisions are sent for review regardless of the status of the '.
'build.')),
);
$land_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::LANDWARNING_ALWAYS)
->setIcon('fa-check-circle-o green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht(
'"arc land" warns if the build is still running or has '.
'failed.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::LANDWARNING_IF_BUILDING)
->setIcon('fa-pause-circle-o yellow')
->setName(pht('If Building'))
->setDescription(
pht(
'"arc land" warns if the build is still running, but ignores '.
'the build if it has failed.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::LANDWARNING_IF_COMPLETE)
->setIcon('fa-dot-circle-o yellow')
->setName(pht('If Complete'))
->setDescription(
pht(
'"arc land" warns if the build has failed, but ignores the '.
'build if it is still running.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::LANDWARNING_NEVER)
->setIcon('fa-circle-o red')
->setName(pht('Never'))
->setDescription(
pht(
'"arc land" never warns that the build is still running or '.
'has failed.')),
);
$aggregate_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::BUILDABLE_ALWAYS)
->setIcon('fa-check-circle-o green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht(
'The buildable waits for the build, and fails if the '.
'build fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::BUILDABLE_IF_BUILDING)
->setIcon('fa-pause-circle-o yellow')
->setName(pht('If Building'))
->setDescription(
pht(
'The buildable waits for the build, but does not fail '.
'if the build fails.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::BUILDABLE_NEVER)
->setIcon('fa-circle-o red')
->setName(pht('Never'))
->setDescription(
pht(
'The buildable does not wait for the build.')),
);
$restart_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_ALWAYS)
->setIcon('fa-repeat green')
->setName(pht('Always'))
->setIsDefault(true)
->setDescription(
pht('The build may be restarted.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_IF_FAILED)
->setIcon('fa-times-circle-o yellow')
->setName(pht('If Failed'))
->setDescription(
pht('The build may be restarted if it has failed.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RESTARTABLE_NEVER)
->setIcon('fa-times red')
->setName(pht('Never'))
->setDescription(
pht('The build may not be restarted.')),
);
$run_options = array(
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RUNNABLE_IF_EDITABLE)
->setIcon('fa-pencil green')
->setName(pht('If Editable'))
->setIsDefault(true)
->setDescription(
pht('Only users who can edit the plan can run it manually.')),
id(new HarbormasterBuildPlanBehaviorOption())
->setKey(self::RUNNABLE_IF_VIEWABLE)
->setIcon('fa-exclamation-triangle yellow')
->setName(pht('If Viewable'))
->setDescription(
pht(
'Any user who can view the plan can run it manually.')),
);
$behaviors = array(
id(new self())
->setKey(self::BEHAVIOR_DRAFTS)
->setName(pht('Hold Drafts'))
->setEditInstructions(
pht(
'When users create revisions in Differential, the default '.
'behavior is to hold them in the "Draft" state until all builds '.
'pass. Once builds pass, the revisions promote and are sent for '.
'review, which notifies reviewers.'.
"\n\n".
'The general intent of this workflow is to make sure reviewers '.
'are only spending time on review once changes survive automated '.
'tests. If a change does not pass tests, it usually is not '.
'really ready for review.'.
"\n\n".
'If you want to promote revisions out of "Draft" before builds '.
'pass, or promote revisions even when builds fail, you can '.
'change the promotion behavior. This may be useful if you have '.
'very long-running builds, or some builds which are not very '.
'important.'.
"\n\n".
'Users may always use "Request Review" to promote a "Draft" '.
'revision, even if builds have failed or are still in progress.'))
->setOptions($draft_options),
id(new self())
->setKey(self::BEHAVIOR_LANDWARNING)
->setName(pht('Warn When Landing'))
->setEditInstructions(
pht(
'When a user attempts to `arc land` a revision and that revision '.
'has ongoing or failed builds, the default behavior of `arc` is '.
'to warn them about those builds and give them a chance to '.
'reconsider: they may want to wait for ongoing builds to '.
'complete, or fix failed builds before landing the change.'.
"\n\n".
'If you do not want to warn users about this build, you can '.
'change the warning behavior. This may be useful if the build '.
'takes a long time to run (so you do not expect users to wait '.
'for it) or the outcome is not important.'.
"\n\n".
'This warning is only advisory. Users may always elect to ignore '.
'this warning and continue, even if builds have failed.'.
"\n\n".
'This setting also affects the warning that is published to '.
'revisions when commits land with ongoing or failed builds.'))
->setOptions($land_options),
id(new self())
->setKey(self::BEHAVIOR_BUILDABLE)
->setEditInstructions(
pht(
'The overall state of a buildable (like a commit or revision) is '.
'normally the aggregation of the individual states of all builds '.
'that have run against it.'.
"\n\n".
'Buildables are "building" until all builds pass (which changes '.
'them to "pass"), or any build fails (which changes them to '.
'"fail").'.
"\n\n".
'You can change this behavior if you do not want to wait for this '.
'build, or do not care if it fails.'))
->setName(pht('Affects Buildable'))
->setOptions($aggregate_options),
id(new self())
->setKey(self::BEHAVIOR_RESTARTABLE)
->setEditInstructions(
pht(
'Usually, builds may be restarted. This may be useful if you '.
'suspect a build has failed for environmental or circumstantial '.
'reasons unrelated to the actual code, and want to give it '.
'another chance at glory.'.
"\n\n".
'If you want to prevent a build from being restarted, you can '.
'change the behavior here. This may be useful to prevent '.
'accidents where a build with a dangerous side effect (like '.
'deployment) is restarted improperly.'))
->setName(pht('Restartable'))
->setOptions($restart_options),
id(new self())
->setKey(self::BEHAVIOR_RUNNABLE)
->setEditInstructions(
pht(
'To run a build manually, you normally must have permission to '.
'edit the related build plan. If you would prefer that anyone who '.
'can see the build plan be able to run and restart the build, you '.
'can change the behavior here.'.
"\n\n".
'Note that this controls access to all build management actions: '.
'"Run Plan Manually", "Restart", "Abort", "Pause", and "Resume".'.
"\n\n".
'WARNING: This may be unsafe, particularly if the build has '.
'side effects like deployment.'.
"\n\n".
'If you weaken this policy, an attacker with control of an '.
'account that has "Can View" permission but not "Can Edit" '.
'permission can manually run this build against any old version '.
'of the code, including versions with known security issues.'.
"\n\n".
'If running the build has a side effect like deploying code, '.
'they can force deployment of a vulnerable version and then '.
'escalate into an attack against the deployed service.'))
->setName(pht('Runnable'))
->setOptions($run_options),
);
return mpull($behaviors, null, 'getKey');
}
}

View file

@ -0,0 +1,57 @@
<?php
final class HarbormasterBuildPlanBehaviorOption
extends Phobject {
private $name;
private $key;
private $icon;
private $description;
private $isDefault;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function getDescription() {
return $this->description;
}
public function setIsDefault($is_default) {
$this->isDefault = $is_default;
return $this;
}
public function getIsDefault() {
return $this->isDefault;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function getIcon() {
return $this->icon;
}
}

View file

@ -128,49 +128,14 @@ final class HarbormasterBuildSearchEngine
$viewer = $this->requireViewer();
$buildables = mpull($builds, 'getBuildable');
$object_phids = mpull($buildables, 'getBuildablePHID');
$initiator_phids = mpull($builds, 'getInitiatorPHID');
$phids = array_mergev(array($initiator_phids, $object_phids));
$phids = array_unique(array_filter($phids));
$list = id(new HarbormasterBuildView())
->setViewer($viewer)
->setBuilds($builds)
->newObjectList();
$handles = $viewer->loadHandles($phids);
$list = new PHUIObjectItemListView();
foreach ($builds as $build) {
$id = $build->getID();
$initiator = $handles[$build->getInitiatorPHID()];
$buildable_object = $handles[$build->getBuildable()->getBuildablePHID()];
$item = id(new PHUIObjectItemView())
->setViewer($viewer)
->setObject($build)
->setObjectName(pht('Build %d', $build->getID()))
->setHeader($build->getName())
->setHref($build->getURI())
->setEpoch($build->getDateCreated())
->addAttribute($buildable_object->getName());
if ($initiator) {
$item->addHandleIcon($initiator, $initiator->getName());
}
$status = $build->getBuildStatus();
$status_icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
$status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
$status_label = HarbormasterBuildStatus::getBuildStatusName($status);
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No builds found.'));
return $result;
return id(new PhabricatorApplicationSearchResultView())
->setObjectList($list)
->setNoDataString(pht('No builds found.'));
}
}

View file

@ -183,6 +183,10 @@ final class HarbormasterBuild extends HarbormasterDAO
return $this->getBuildStatusObject()->isPassed();
}
public function isFailed() {
return $this->getBuildStatusObject()->isFailed();
}
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
@ -193,6 +197,10 @@ final class HarbormasterBuild extends HarbormasterDAO
return HarbormasterBuildStatus::newBuildStatusObject($status_key);
}
public function getObjectName() {
return pht('Build %d', $this->getID());
}
/* -( Build Commands )----------------------------------------------------- */
@ -207,11 +215,60 @@ final class HarbormasterBuild extends HarbormasterDAO
}
public function canRestartBuild() {
if ($this->isAutobuild()) {
try {
$this->assertCanRestartBuild();
return true;
} catch (HarbormasterRestartException $ex) {
return false;
}
}
return !$this->isRestarting();
public function assertCanRestartBuild() {
if ($this->isAutobuild()) {
throw new HarbormasterRestartException(
pht('Can Not Restart Autobuild'),
pht(
'This build can not be restarted because it is an automatic '.
'build.'));
}
$restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
$plan = $this->getBuildPlan();
$option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
->getPlanOption($plan);
$option_key = $option->getKey();
$never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER;
$is_never = ($option_key === $never_restartable);
if ($is_never) {
throw new HarbormasterRestartException(
pht('Build Plan Prevents Restart'),
pht(
'This build can not be restarted because the build plan is '.
'configured to prevent the build from restarting.'));
}
$failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED;
$is_failed = ($option_key === $failed_restartable);
if ($is_failed) {
if (!$this->isFailed()) {
throw new HarbormasterRestartException(
pht('Only Restartable if Failed'),
pht(
'This build can not be restarted because the build plan is '.
'configured to prevent the build from restarting unless it '.
'has failed, and it has not failed.'));
}
}
if ($this->isRestarting()) {
throw new HarbormasterRestartException(
pht('Already Restarting'),
pht(
'This build is already restarting. You can not reissue a restart '.
'command to a restarting build.'));
}
}
public function canPauseBuild() {
@ -330,14 +387,17 @@ final class HarbormasterBuild extends HarbormasterDAO
}
public function assertCanIssueCommand(PhabricatorUser $viewer, $command) {
$need_edit = false;
$plan = $this->getBuildPlan();
$need_edit = true;
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
break;
case HarbormasterBuildCommand::COMMAND_PAUSE:
case HarbormasterBuildCommand::COMMAND_RESUME:
case HarbormasterBuildCommand::COMMAND_ABORT:
$need_edit = true;
if ($plan->canRunWithoutEditCapability()) {
$need_edit = false;
}
break;
default:
throw new Exception(
@ -351,7 +411,7 @@ final class HarbormasterBuild extends HarbormasterDAO
if ($need_edit) {
PhabricatorPolicyFilter::requireCapability(
$viewer,
$this->getBuildPlan(),
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
}
}

View file

@ -10,13 +10,15 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
PhabricatorSubscribableInterface,
PhabricatorNgramsInterface,
PhabricatorConduitResultInterface,
PhabricatorProjectInterface {
PhabricatorProjectInterface,
PhabricatorPolicyCodexInterface {
protected $name;
protected $planStatus;
protected $planAutoKey;
protected $viewPolicy;
protected $editPolicy;
protected $properties = array();
const STATUS_ACTIVE = 'active';
const STATUS_DISABLED = 'disabled';
@ -45,6 +47,9 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128',
'planStatus' => 'text32',
@ -84,6 +89,25 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return ($this->getPlanStatus() == self::STATUS_DISABLED);
}
public function getURI() {
return urisprintf(
'/harbormaster/plan/%s/',
$this->getID());
}
public function getObjectName() {
return pht('Plan %d', $this->getID());
}
public function getPlanProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setPlanProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
/* -( Autoplans )---------------------------------------------------------- */
@ -110,7 +134,6 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return true;
}
public function getName() {
$autoplan = $this->getAutoplan();
if ($autoplan) {
@ -120,6 +143,38 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return parent::getName();
}
public function hasRunCapability(PhabricatorUser $viewer) {
try {
$this->assertHasRunCapability($viewer);
return true;
} catch (PhabricatorPolicyException $ex) {
return false;
}
}
public function canRunWithoutEditCapability() {
$runnable = HarbormasterBuildPlanBehavior::BEHAVIOR_RUNNABLE;
$if_viewable = HarbormasterBuildPlanBehavior::RUNNABLE_IF_VIEWABLE;
$option = HarbormasterBuildPlanBehavior::getBehavior($runnable)
->getPlanOption($this);
return ($option->getKey() === $if_viewable);
}
public function assertHasRunCapability(PhabricatorUser $viewer) {
if ($this->canRunWithoutEditCapability()) {
$capability = PhabricatorPolicyCapability::CAN_VIEW;
} else {
$capability = PhabricatorPolicyCapability::CAN_EDIT;
}
PhabricatorPolicyFilter::requireCapability(
$viewer,
$this,
$capability);
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
@ -210,15 +265,31 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
->setKey('status')
->setType('map<string, wild>')
->setDescription(pht('The current status of this build plan.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('behaviors')
->setType('map<string, string>')
->setDescription(pht('Behavior configuration for the build plan.')),
);
}
public function getFieldValuesForConduit() {
$behavior_map = array();
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
foreach ($behaviors as $behavior) {
$option = $behavior->getPlanOption($this);
$behavior_map[$behavior->getKey()] = array(
'value' => $option->getKey(),
);
}
return array(
'name' => $this->getName(),
'status' => array(
'value' => $this->getPlanStatus(),
),
'behaviors' => $behavior_map,
);
}
@ -226,4 +297,12 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return array();
}
/* -( PhabricatorPolicyCodexInterface )------------------------------------ */
public function newPolicyCodex() {
return new HarbormasterBuildPlanPolicyCodex();
}
}

View file

@ -1,10 +1,7 @@
<?php
final class HarbormasterBuildPlanTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'harbormaster:name';
const TYPE_STATUS = 'harbormaster:status';
extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'harbormaster';
@ -14,67 +11,8 @@ final class HarbormasterBuildPlanTransaction
return HarbormasterBuildPlanPHIDType::TYPECONST;
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
if ($old === null) {
return 'fa-plus';
}
break;
}
return parent::getIcon();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
if ($old === null) {
return 'green';
}
break;
}
return parent::getIcon();
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
$author_handle = $this->renderHandleLink($this->getAuthorPHID());
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this build plan.',
$author_handle);
} else {
return pht(
'%s renamed this build plan from "%s" to "%s".',
$author_handle,
$old,
$new);
}
case self::TYPE_STATUS:
if ($new == HarbormasterBuildPlan::STATUS_DISABLED) {
return pht(
'%s disabled this build plan.',
$author_handle);
} else {
return pht(
'%s enabled this build plan.',
$author_handle);
}
}
return parent::getTitle();
public function getBaseTransactionClass() {
return 'HarbormasterBuildPlanTransactionType';
}
}

View file

@ -0,0 +1,67 @@
<?php
final class HarbormasterBuildView
extends AphrontView {
private $builds = array();
public function setBuilds(array $builds) {
assert_instances_of($builds, 'HarbormasterBuild');
$this->builds = $builds;
return $this;
}
public function getBuilds() {
return $this->builds;
}
public function render() {
return $this->newObjectList();
}
public function newObjectList() {
$viewer = $this->getViewer();
$builds = $this->getBuilds();
$buildables = mpull($builds, 'getBuildable');
$object_phids = mpull($buildables, 'getBuildablePHID');
$initiator_phids = mpull($builds, 'getInitiatorPHID');
$phids = array_mergev(array($initiator_phids, $object_phids));
$phids = array_unique(array_filter($phids));
$handles = $viewer->loadHandles($phids);
$list = new PHUIObjectItemListView();
foreach ($builds as $build) {
$id = $build->getID();
$initiator = $handles[$build->getInitiatorPHID()];
$buildable_object = $handles[$build->getBuildable()->getBuildablePHID()];
$item = id(new PHUIObjectItemView())
->setViewer($viewer)
->setObject($build)
->setObjectName($build->getObjectName())
->setHeader($build->getName())
->setHref($build->getURI())
->setEpoch($build->getDateCreated())
->addAttribute($buildable_object->getName());
if ($initiator) {
$item->addByline($initiator->renderLink());
}
$status = $build->getBuildStatus();
$status_icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
$status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
$status_label = HarbormasterBuildStatus::getBuildStatusName($status);
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
$list->addItem($item);
}
return $list;
}
}

View file

@ -0,0 +1,127 @@
<?php
final class HarbormasterBuildPlanBehaviorTransaction
extends HarbormasterBuildPlanTransactionType {
const TRANSACTIONTYPE = 'behavior';
public function generateOldValue($object) {
$behavior = $this->getBehavior();
return $behavior->getPlanOption($object)->getKey();
}
public function applyInternalEffects($object, $value) {
$key = $this->getStorageKey();
return $object->setPlanProperty($key, $value);
}
public function getTitle() {
$old_value = $this->getOldValue();
$new_value = $this->getNewValue();
$behavior = $this->getBehavior();
if ($behavior) {
$behavior_name = $behavior->getName();
$options = $behavior->getOptions();
if (isset($options[$old_value])) {
$old_value = $options[$old_value]->getName();
}
if (isset($options[$new_value])) {
$new_value = $options[$new_value]->getName();
}
} else {
$behavior_name = $this->getBehaviorKey();
}
return pht(
'%s changed the %s behavior for this plan from %s to %s.',
$this->renderAuthor(),
$this->renderValue($behavior_name),
$this->renderValue($old_value),
$this->renderValue($new_value));
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
$behaviors = mpull($behaviors, null, 'getKey');
foreach ($xactions as $xaction) {
$key = $this->getBehaviorKeyForTransaction($xaction);
if (!isset($behaviors[$key])) {
$errors[] = $this->newInvalidError(
pht(
'No behavior with key "%s" exists. Valid keys are: %s.',
$key,
implode(', ', array_keys($behaviors))),
$xaction);
continue;
}
$behavior = $behaviors[$key];
$options = $behavior->getOptions();
$storage_key = HarbormasterBuildPlanBehavior::getStorageKeyForBehaviorKey(
$key);
$old = $object->getPlanProperty($storage_key);
$new = $xaction->getNewValue();
if ($old === $new) {
continue;
}
if (!isset($options[$new])) {
$errors[] = $this->newInvalidError(
pht(
'Value "%s" is not a valid option for behavior "%s". Valid '.
'options are: %s.',
$new,
$key,
implode(', ', array_keys($options))),
$xaction);
continue;
}
}
return $errors;
}
public function getTransactionTypeForConduit($xaction) {
return 'behavior';
}
public function getFieldValuesForConduit($xaction, $data) {
return array(
'key' => $this->getBehaviorKeyForTransaction($xaction),
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
);
}
private function getBehaviorKeyForTransaction(
PhabricatorApplicationTransaction $xaction) {
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
return $xaction->getMetadataValue($metadata_key);
}
private function getBehaviorKey() {
$metadata_key = HarbormasterBuildPlanBehavior::getTransactionMetadataKey();
return $this->getMetadataValue($metadata_key);
}
private function getBehavior() {
$behavior_key = $this->getBehaviorKey();
$behaviors = HarbormasterBuildPlanBehavior::newPlanBehaviors();
return idx($behaviors, $behavior_key);
}
private function getStorageKey() {
return HarbormasterBuildPlanBehavior::getStorageKeyForBehaviorKey(
$this->getBehaviorKey());
}
}

View file

@ -0,0 +1,46 @@
<?php
final class HarbormasterBuildPlanNameTransaction
extends HarbormasterBuildPlanTransactionType {
const TRANSACTIONTYPE = 'harbormaster:name';
public function generateOldValue($object) {
return $object->getName();
}
public function applyInternalEffects($object, $value) {
$object->setName($value);
}
public function getTitle() {
return pht(
'%s renamed this build plan from "%s" to "%s".',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
$errors[] = $this->newRequiredError(
pht('You must choose a name for your build plan.'));
}
return $errors;
}
public function getTransactionTypeForConduit($xaction) {
return 'name';
}
public function getFieldValuesForConduit($xaction, $data) {
return array(
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
);
}
}

View file

@ -0,0 +1,67 @@
<?php
final class HarbormasterBuildPlanStatusTransaction
extends HarbormasterBuildPlanTransactionType {
const TRANSACTIONTYPE = 'harbormaster:status';
public function generateOldValue($object) {
return $object->getPlanStatus();
}
public function applyInternalEffects($object, $value) {
$object->setPlanStatus($value);
}
public function getTitle() {
$new = $this->getNewValue();
if ($new === HarbormasterBuildPlan::STATUS_DISABLED) {
return pht(
'%s disabled this build plan.',
$this->renderAuthor());
} else {
return pht(
'%s enabled this build plan.',
$this->renderAuthor());
}
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$options = array(
HarbormasterBuildPlan::STATUS_DISABLED,
HarbormasterBuildPlan::STATUS_ACTIVE,
);
$options = array_fuse($options);
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
if (!isset($options[$new])) {
$errors[] = $this->newInvalidError(
pht(
'Status "%s" is not a valid build plan status. Valid '.
'statuses are: %s.',
$new,
implode(', ', $options)));
continue;
}
}
return $errors;
}
public function getTransactionTypeForConduit($xaction) {
return 'status';
}
public function getFieldValuesForConduit($xaction, $data) {
return array(
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
);
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class HarbormasterBuildPlanTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -401,4 +401,8 @@ abstract class HeraldAction extends Phobject {
return null;
}
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
return array();
}
}

View file

@ -63,4 +63,8 @@ final class HeraldCallWebhookAction extends HeraldAction {
return new HeraldWebhookDatasource();
}
public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
return $record->getTarget();
}
}

View file

@ -186,15 +186,16 @@ abstract class HeraldAdapter extends Phobject {
return $this->appliedTransactions;
}
public function queueTransaction($transaction) {
final public function queueTransaction(
PhabricatorApplicationTransaction $transaction) {
$this->queuedTransactions[] = $transaction;
}
public function getQueuedTransactions() {
final public function getQueuedTransactions() {
return $this->queuedTransactions;
}
public function newTransaction() {
final public function newTransaction() {
$object = $this->newObject();
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
@ -205,7 +206,19 @@ abstract class HeraldAdapter extends Phobject {
'PhabricatorApplicationTransactionInterface'));
}
return $object->getApplicationTransactionTemplate();
$xaction = $object->getApplicationTransactionTemplate();
if (!($xaction instanceof PhabricatorApplicationTransaction)) {
throw new Exception(
pht(
'Expected object (of class "%s") to return a transaction template '.
'(of class "%s"), but it returned something else ("%s").',
get_class($object),
'PhabricatorApplicationTransaction',
phutil_describe_type($xaction)));
}
return $xaction;
}

View file

@ -31,7 +31,7 @@ final class HeraldDisableController extends HeraldController {
if ($request->isFormPost()) {
$xaction = id(new HeraldRuleTransaction())
->setTransactionType(HeraldRuleTransaction::TYPE_DISABLE)
->setTransactionType(HeraldRuleDisableTransaction::TRANSACTIONTYPE)
->setNewValue($is_disable);
id(new HeraldRuleEditor())

View file

@ -359,11 +359,21 @@ final class HeraldRuleController extends HeraldController {
$repetition_policy);
$xactions = array();
// Until this moves to EditEngine, manually add a "CREATE" transaction
// if we're creating a new rule. This improves rendering of the initial
// group of transactions.
$is_new = (bool)(!$rule->getID());
if ($is_new) {
$xactions[] = id(new HeraldRuleTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
}
$xactions[] = id(new HeraldRuleTransaction())
->setTransactionType(HeraldRuleTransaction::TYPE_EDIT)
->setTransactionType(HeraldRuleEditTransaction::TRANSACTIONTYPE)
->setNewValue($new_state);
$xactions[] = id(new HeraldRuleTransaction())
->setTransactionType(HeraldRuleTransaction::TYPE_NAME)
->setTransactionType(HeraldRuleNameTransaction::TRANSACTIONTYPE)
->setNewValue($new_name);
try {

View file

@ -73,12 +73,15 @@ final class HeraldWebhookViewController
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($requests_table);
$rules_view = $this->newRulesView($hook);
$hook_view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(
array(
$warnings,
$properties_view,
$rules_view,
$requests_view,
$timeline,
))
@ -194,4 +197,42 @@ final class HeraldWebhookViewController
->appendChild($properties);
}
private function newRulesView(HeraldWebhook $hook) {
$viewer = $this->getViewer();
$rules = id(new HeraldRuleQuery())
->setViewer($viewer)
->withDisabled(false)
->withAffectedObjectPHIDs(array($hook->getPHID()))
->needValidateAuthors(true)
->setLimit(10)
->execute();
$list = id(new HeraldRuleListView())
->setViewer($viewer)
->setRules($rules)
->newObjectList();
$list->setNoDataString(pht('No active Herald rules call this webhook.'));
$more_href = new PhutilURI(
'/herald/',
array('affectedPHID' => $hook->getPHID()));
$more_link = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-list-ul')
->setText(pht('View All Rules'))
->setHref($more_href);
$header = id(new PHUIHeaderView())
->setHeader(pht('Called By Herald Rules'))
->addActionLink($more_link);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
}
}

View file

@ -0,0 +1,8 @@
<?php
final class HeraldRuleActionAffectsObjectEdgeType
extends PhabricatorEdgeType {
const EDGECONST = 69;
}

View file

@ -11,82 +11,6 @@ final class HeraldRuleEditor
return pht('Herald Rules');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = HeraldRuleTransaction::TYPE_EDIT;
$types[] = HeraldRuleTransaction::TYPE_NAME;
$types[] = HeraldRuleTransaction::TYPE_DISABLE;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HeraldRuleTransaction::TYPE_DISABLE:
return (int)$object->getIsDisabled();
case HeraldRuleTransaction::TYPE_EDIT:
return id(new HeraldRuleSerializer())
->serializeRule($object);
case HeraldRuleTransaction::TYPE_NAME:
return $object->getName();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HeraldRuleTransaction::TYPE_DISABLE:
return (int)$xaction->getNewValue();
case HeraldRuleTransaction::TYPE_EDIT:
case HeraldRuleTransaction::TYPE_NAME:
return $xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HeraldRuleTransaction::TYPE_DISABLE:
return $object->setIsDisabled($xaction->getNewValue());
case HeraldRuleTransaction::TYPE_NAME:
return $object->setName($xaction->getNewValue());
case HeraldRuleTransaction::TYPE_EDIT:
$new_state = id(new HeraldRuleSerializer())
->deserializeRuleComponents($xaction->getNewValue());
$object->setMustMatchAll((int)$new_state['match_all']);
$object->attachConditions($new_state['conditions']);
$object->attachActions($new_state['actions']);
$new_repetition = $new_state['repetition_policy'];
$object->setRepetitionPolicyStringConstant($new_repetition);
return $object;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case HeraldRuleTransaction::TYPE_EDIT:
$object->saveConditions($object->getConditions());
$object->saveActions($object->getActions());
break;
}
return;
}
protected function shouldApplyHeraldRules(
PhabricatorLiskDAO $object,
array $xactions) {
@ -137,7 +61,6 @@ final class HeraldRuleEditor
return pht('[Herald]');
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
@ -151,4 +74,8 @@ final class HeraldRuleEditor
return $body;
}
protected function supportsSearch() {
return true;
}
}

View file

@ -0,0 +1,92 @@
<?php
final class HeraldRuleIndexEngineExtension
extends PhabricatorIndexEngineExtension {
const EXTENSIONKEY = 'herald.actions';
public function getExtensionName() {
return pht('Herald Actions');
}
public function shouldIndexObject($object) {
if (!($object instanceof HeraldRule)) {
return false;
}
return true;
}
public function indexObject(
PhabricatorIndexEngine $engine,
$object) {
$edge_type = HeraldRuleActionAffectsObjectEdgeType::EDGECONST;
$old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
$edge_type);
$old_edges = array_fuse($old_edges);
$new_edges = $this->getPHIDsAffectedByActions($object);
$new_edges = array_fuse($new_edges);
$add_edges = array_diff_key($new_edges, $old_edges);
$rem_edges = array_diff_key($old_edges, $new_edges);
if (!$add_edges && !$rem_edges) {
return;
}
$editor = new PhabricatorEdgeEditor();
foreach ($add_edges as $phid) {
$editor->addEdge($object->getPHID(), $edge_type, $phid);
}
foreach ($rem_edges as $phid) {
$editor->removeEdge($object->getPHID(), $edge_type, $phid);
}
$editor->save();
}
public function getIndexVersion($object) {
$phids = $this->getPHIDsAffectedByActions($object);
sort($phids);
$phids = implode(':', $phids);
return PhabricatorHash::digestForIndex($phids);
}
private function getPHIDsAffectedByActions(HeraldRule $rule) {
$viewer = $this->getViewer();
$rule = id(new HeraldRuleQuery())
->setViewer($viewer)
->withIDs(array($rule->getID()))
->needConditionsAndActions(true)
->executeOne();
if (!$rule) {
return array();
}
$phids = array();
$actions = HeraldAction::getAllActions();
foreach ($rule->getActions() as $action_record) {
$action = idx($actions, $action_record->getAction());
if (!$action) {
continue;
}
foreach ($action->getPHIDsAffectedByAction($action_record) as $phid) {
$phids[] = $phid;
}
}
$phids = array_fuse($phids);
return array_keys($phids);
}
}

View file

@ -11,6 +11,7 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $active;
private $datasourceQuery;
private $triggerObjectPHIDs;
private $affectedObjectPHIDs;
private $needConditionsAndActions;
private $needAppliedToPHIDs;
@ -61,6 +62,11 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
public function withAffectedObjectPHIDs(array $phids) {
$this->affectedObjectPHIDs = $phids;
return $this;
}
public function needConditionsAndActions($need) {
$this->needConditionsAndActions = $need;
return $this;
@ -261,9 +267,31 @@ final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->triggerObjectPHIDs);
}
if ($this->affectedObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'edge_affects.dst IN (%Ls)',
$this->affectedObjectPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->affectedObjectPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T edge_affects ON rule.phid = edge_affects.src
AND edge_affects.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
HeraldRuleActionAffectsObjectEdgeType::EDGECONST);
}
return $joins;
}
private function validateRuleAuthors(array $rules) {
// "Global" and "Object" rules always have valid authors.
foreach ($rules as $key => $rule) {

View file

@ -55,6 +55,10 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
pht('(Show All)'),
pht('Show Only Disabled Rules'),
pht('Show Only Enabled Rules')),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Affected Objects'))
->setKey('affectedPHIDs')
->setAliases(array('affectedPHID')),
);
}
@ -81,6 +85,10 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
$query->withActive($map['active']);
}
if ($map['affectedPHIDs']) {
$query->withAffectedObjectPHIDs($map['affectedPHIDs']);
}
return $query;
}
@ -127,54 +135,18 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine {
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($rules, 'HeraldRule');
$viewer = $this->requireViewer();
$handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID'));
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
$list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($rules as $rule) {
$monogram = $rule->getMonogram();
$item = id(new PHUIObjectItemView())
->setObjectName($monogram)
->setHeader($rule->getName())
->setHref("/{$monogram}");
if ($rule->isPersonalRule()) {
$item->addIcon('fa-user', pht('Personal Rule'));
$item->addByline(
pht(
'Authored by %s',
$handles[$rule->getAuthorPHID()]->renderLink()));
} else if ($rule->isObjectRule()) {
$item->addIcon('fa-briefcase', pht('Object Rule'));
} else {
$item->addIcon('fa-globe', pht('Global Rule'));
}
if ($rule->getIsDisabled()) {
$item->setDisabled(true);
$item->addIcon('fa-lock grey', pht('Disabled'));
} else if (!$rule->hasValidAuthor()) {
$item->setDisabled(true);
$item->addIcon('fa-user grey', pht('Author Not Active'));
}
$content_type_name = idx($content_type_map, $rule->getContentType());
$item->addAttribute(pht('Affects: %s', $content_type_name));
$list->addItem($item);
}
$list = id(new HeraldRuleListView())
->setViewer($viewer)
->setRules($rules)
->newObjectList();
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No rules found.'));
return $result;
}
protected function getNewUserBody() {

View file

@ -6,6 +6,7 @@ final class HeraldRule extends HeraldDAO
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface,
PhabricatorIndexableInterface,
PhabricatorSubscribableInterface {
const TABLE_RULE_APPLIED = 'herald_ruleapplied';

View file

@ -1,11 +1,9 @@
<?php
final class HeraldRuleTransaction
extends PhabricatorApplicationTransaction {
extends PhabricatorModularTransaction {
const TYPE_EDIT = 'herald:edit';
const TYPE_NAME = 'herald:name';
const TYPE_DISABLE = 'herald:disable';
public function getApplicationName() {
return 'herald';
@ -15,121 +13,8 @@ final class HeraldRuleTransaction
return HeraldRulePHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new HeraldRuleTransactionComment();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_DISABLE:
if ($new) {
return 'red';
} else {
return 'green';
}
}
return parent::getColor();
}
public function getActionName() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_DISABLE:
if ($new) {
return pht('Disabled');
} else {
return pht('Enabled');
}
case self::TYPE_NAME:
return pht('Renamed');
}
return parent::getActionName();
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_DISABLE:
if ($new) {
return 'fa-ban';
} else {
return 'fa-check';
}
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_DISABLE:
if ($new) {
return pht(
'%s disabled this rule.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s enabled this rule.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_NAME:
if ($old == null) {
return pht(
'%s created this rule.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this rule from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
case self::TYPE_EDIT:
return pht(
'%s edited this rule.',
$this->renderHandleLink($author_phid));
}
return parent::getTitle();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_EDIT:
return true;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
$json = new PhutilJSON();
switch ($this->getTransactionType()) {
case self::TYPE_EDIT:
return $this->renderTextCorpusChangeDetails(
$viewer,
$json->encodeFormatted($this->getOldValue()),
$json->encodeFormatted($this->getNewValue()));
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
public function getBaseTransactionClass() {
return 'HeraldRuleTransactionType';
}
}

View file

@ -1,10 +0,0 @@
<?php
final class HeraldRuleTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new HeraldRuleTransaction();
}
}

View file

@ -0,0 +1,65 @@
<?php
final class HeraldRuleListView
extends AphrontView {
private $rules;
public function setRules(array $rules) {
assert_instances_of($rules, 'HeraldRule');
$this->rules = $rules;
return $this;
}
public function render() {
return $this->newObjectList();
}
public function newObjectList() {
$viewer = $this->getViewer();
$rules = $this->rules;
$handles = $viewer->loadHandles(mpull($rules, 'getAuthorPHID'));
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
$list = id(new PHUIObjectItemListView())
->setViewer($viewer);
foreach ($rules as $rule) {
$monogram = $rule->getMonogram();
$item = id(new PHUIObjectItemView())
->setObjectName($monogram)
->setHeader($rule->getName())
->setHref($rule->getURI());
if ($rule->isPersonalRule()) {
$item->addIcon('fa-user', pht('Personal Rule'));
$item->addByline(
pht(
'Authored by %s',
$handles[$rule->getAuthorPHID()]->renderLink()));
} else if ($rule->isObjectRule()) {
$item->addIcon('fa-briefcase', pht('Object Rule'));
} else {
$item->addIcon('fa-globe', pht('Global Rule'));
}
if ($rule->getIsDisabled()) {
$item->setDisabled(true);
$item->addIcon('fa-lock grey', pht('Disabled'));
} else if (!$rule->hasValidAuthor()) {
$item->setDisabled(true);
$item->addIcon('fa-user grey', pht('Author Not Active'));
}
$content_type_name = idx($content_type_map, $rule->getContentType());
$item->addAttribute(pht('Affects: %s', $content_type_name));
$list->addItem($item);
}
return $list;
}
}

View file

@ -0,0 +1,32 @@
<?php
final class HeraldRuleDisableTransaction
extends HeraldRuleTransactionType {
const TRANSACTIONTYPE = 'herald:disable';
public function generateOldValue($object) {
return (bool)$object->getIsDisabled();
}
public function generateNewValue($object, $value) {
return (bool)$value;
}
public function applyInternalEffects($object, $value) {
$object->setIsDisabled((int)$value);
}
public function getTitle() {
if ($this->getNewValue()) {
return pht(
'%s disabled this rule.',
$this->renderAuthor());
} else {
return pht(
'%s enabled this rule.',
$this->renderAuthor());
}
}
}

View file

@ -0,0 +1,56 @@
<?php
final class HeraldRuleEditTransaction
extends HeraldRuleTransactionType {
const TRANSACTIONTYPE = 'herald:edit';
public function generateOldValue($object) {
return id(new HeraldRuleSerializer())
->serializeRule($object);
}
public function applyInternalEffects($object, $value) {
$new_state = id(new HeraldRuleSerializer())
->deserializeRuleComponents($value);
$object->setMustMatchAll((int)$new_state['match_all']);
$object->attachConditions($new_state['conditions']);
$object->attachActions($new_state['actions']);
$new_repetition = $new_state['repetition_policy'];
$object->setRepetitionPolicyStringConstant($new_repetition);
}
public function applyExternalEffects($object, $value) {
$object->saveConditions($object->getConditions());
$object->saveActions($object->getActions());
}
public function getTitle() {
return pht(
'%s edited this rule.',
$this->renderAuthor());
}
public function hasChangeDetailView() {
return true;
}
public function newChangeDetailView() {
$viewer = $this->getViewer();
$old = $this->getOldValue();
$new = $this->getNewValue();
$json = new PhutilJSON();
$old_json = $json->encodeFormatted($old);
$new_json = $json->encodeFormatted($new);
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setViewer($viewer)
->setOldText($old_json)
->setNewText($new_json);
}
}

View file

@ -0,0 +1,48 @@
<?php
final class HeraldRuleNameTransaction
extends HeraldRuleTransactionType {
const TRANSACTIONTYPE = 'herald:name';
public function generateOldValue($object) {
return $object->getName();
}
public function applyInternalEffects($object, $value) {
$object->setName($value);
}
public function getTitle() {
return pht(
'%s renamed this rule from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
if ($this->isEmptyTextTransaction($object->getName(), $xactions)) {
$errors[] = $this->newRequiredError(
pht('Rules must have a name.'));
}
$max_length = $object->getColumnMaximumByteLength('name');
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
$new_length = strlen($new_value);
if ($new_length > $max_length) {
$errors[] = $this->newInvalidError(
pht(
'Rule names can be no longer than %s characters.',
new PhutilNumber($max_length)));
}
}
return $errors;
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class HeraldRuleTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -0,0 +1,14 @@
<?php
final class ManiphestTaskUnlockEngine
extends PhabricatorUnlockEngine {
public function newUnlockOwnerTransactions($object, $user) {
return array(
$this->newTransaction($object)
->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
->setNewValue($user->getPHID()),
);
}
}

View file

@ -21,7 +21,8 @@ final class ManiphestTask extends ManiphestDAO
PhabricatorEditEngineSubtypeInterface,
PhabricatorEditEngineLockableInterface,
PhabricatorEditEngineMFAInterface,
PhabricatorPolicyCodexInterface {
PhabricatorPolicyCodexInterface,
PhabricatorUnlockableInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
@ -649,4 +650,12 @@ final class ManiphestTask extends ManiphestDAO
return new ManiphestTaskPolicyCodex();
}
/* -( PhabricatorUnlockableInterface )------------------------------------- */
public function newUnlockEngine() {
return new ManiphestTaskUnlockEngine();
}
}

View file

@ -123,4 +123,14 @@ final class ManiphestTaskUnblockTransaction
return parent::shouldHideForFeed();
}
public function getRequiredCapabilities(
$object,
PhabricatorApplicationTransaction $xaction) {
// When you close a task, we want to apply this transaction to its parents
// even if you can not edit (or even see) those parents, so don't require
// any capabilities. See PHI1059.
return null;
}
}

View file

@ -8,40 +8,72 @@ final class PhabricatorPolicyManagementUnlockWorkflow
->setName('unlock')
->setSynopsis(
pht(
'Unlock an object by setting its policies to allow anyone to view '.
'and edit it.'))
->setExamples('**unlock** D123')
'Unlock an object which has policies that prevent it from being '.
'viewed or edited.'))
->setExamples('**unlock** --view __user__ __object__')
->setArguments(
array(
array(
'name' => 'objects',
'wildcard' => true,
'name' => 'view',
'param' => 'username',
'help' => pht(
'Change the view policy of an object so that the specified '.
'user may view it.'),
),
array(
'name' => 'edit',
'param' => 'username',
'help' => pht(
'Change the edit policy of an object so that the specified '.
'user may edit it.'),
),
array(
'name' => 'owner',
'param' => 'username',
'help' => pht(
'Change the owner of an object to the specified user.'),
),
array(
'name' => 'objects',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$viewer = $this->getViewer();
$obj_names = $args->getArg('objects');
if (!$obj_names) {
$object_names = $args->getArg('objects');
if (!$object_names) {
throw new PhutilArgumentUsageException(
pht('Specify the name of an object to unlock.'));
} else if (count($obj_names) > 1) {
} else if (count($object_names) > 1) {
throw new PhutilArgumentUsageException(
pht('Specify the name of exactly one object to unlock.'));
}
$object_name = head($object_names);
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames($obj_names)
->withNames(array($object_name))
->executeOne();
if (!$object) {
$name = head($obj_names);
throw new PhutilArgumentUsageException(
pht("No such object '%s'!", $name));
pht(
'Unable to find any object with the specified name ("%s").',
$object_name));
}
$view_user = $this->loadUser($args->getArg('view'));
$edit_user = $this->loadUser($args->getArg('edit'));
$owner_user = $this->loadUser($args->getArg('owner'));
if (!$view_user && !$edit_user && !$owner_user) {
throw new PhutilArgumentUsageException(
pht(
'Choose which capabilities to unlock with "--view", "--edit", '.
'or "--owner".'));
}
$handle = id(new PhabricatorHandleQuery())
@ -49,84 +81,73 @@ final class PhabricatorPolicyManagementUnlockWorkflow
->withPHIDs(array($object->getPHID()))
->executeOne();
if ($object instanceof PhabricatorApplication) {
$application = $object;
echo tsprintf(
"<bg:blue>** %s **</bg> %s\n",
pht('UNLOCKING'),
pht('Unlocking: %s', $handle->getFullName()));
$console->writeOut(
"%s\n",
pht('Unlocking Application: %s', $handle->getFullName()));
$engine = PhabricatorUnlockEngine::newUnlockEngineForObject($object);
// For applications, we can't unlock them in a normal way and don't want
// to unlock every capability, just view and edit.
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
$xactions = array();
if ($view_user) {
$xactions[] = $engine->newUnlockViewTransactions($object, $view_user);
}
if ($edit_user) {
$xactions[] = $engine->newUnlockEditTransactions($object, $edit_user);
}
if ($owner_user) {
$xactions[] = $engine->newUnlockOwnerTransactions($object, $owner_user);
}
$xactions = array_mergev($xactions);
$key = 'phabricator.application-settings';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$value = $config_entry->getValue();
$policy_application = new PhabricatorPolicyApplication();
$content_source = $this->newContentSource();
foreach ($capabilities as $capability) {
if ($application->isCapabilityEditable($capability)) {
unset($value[$application->getPHID()]['policy'][$capability]);
}
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setActingAsPHID($policy_application->getPHID())
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->setContentSource($content_source);
$config_entry->setValue($value);
$config_entry->save();
$editor->applyTransactions($object, $xactions);
$console->writeOut("%s\n", pht('Saved application.'));
echo tsprintf(
"<bg:green>** %s **</bg> %s\n",
pht('UNLOCKED'),
pht('Modified object policies.'));
return 0;
$uri = $handle->getURI();
if (strlen($uri)) {
echo tsprintf(
"\n **%s**: __%s__\n\n",
pht('Object URI'),
PhabricatorEnv::getURI($uri));
}
$console->writeOut("%s\n", pht('Unlocking: %s', $handle->getFullName()));
return 0;
}
$updated = false;
foreach ($object->getCapabilities() as $capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
try {
$object->setViewPolicy(PhabricatorPolicies::POLICY_USER);
$console->writeOut("%s\n", pht('Unlocked view policy.'));
$updated = true;
} catch (Exception $ex) {
$console->writeOut("%s\n", pht('View policy is not mutable.'));
}
break;
case PhabricatorPolicyCapability::CAN_EDIT:
try {
$object->setEditPolicy(PhabricatorPolicies::POLICY_USER);
$console->writeOut("%s\n", pht('Unlocked edit policy.'));
$updated = true;
} catch (Exception $ex) {
$console->writeOut("%s\n", pht('Edit policy is not mutable.'));
}
break;
case PhabricatorPolicyCapability::CAN_JOIN:
try {
$object->setJoinPolicy(PhabricatorPolicies::POLICY_USER);
$console->writeOut("%s\n", pht('Unlocked join policy.'));
$updated = true;
} catch (Exception $ex) {
$console->writeOut("%s\n", pht('Join policy is not mutable.'));
}
break;
}
private function loadUser($username) {
$viewer = $this->getViewer();
if ($username === null) {
return null;
}
if ($updated) {
$object->save();
$console->writeOut("%s\n", pht('Saved object.'));
} else {
$console->writeOut(
"%s\n",
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($username))
->executeOne();
if (!$user) {
throw new PhutilArgumentUsageException(
pht(
'Object has no mutable policies. Try unlocking parent/container '.
'object instead. For example, to gain access to a commit, unlock '.
'the repository it belongs to.'));
'No user with username "%s" exists.',
$username));
}
return $user;
}
}

View file

@ -124,29 +124,6 @@ final class PhabricatorRepositoryIdentityQuery
return $where;
}
protected function didFilterPage(array $identities) {
$user_ids = array_filter(
mpull($identities, 'getCurrentEffectiveUserPHID', 'getID'));
if (!$user_ids) {
return $identities;
}
$users = id(new PhabricatorPeopleQuery())
->withPHIDs($user_ids)
->setViewer($this->getViewer())
->execute();
$users = mpull($users, null, 'getPHID');
foreach ($identities as $identity) {
if ($identity->hasEffectiveUser()) {
$user = idx($users, $identity->getCurrentEffectiveUserPHID());
$identity->attachEffectiveUser($user);
}
}
return $identities;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}

View file

@ -14,17 +14,6 @@ final class PhabricatorRepositoryIdentity
protected $manuallySetUserPHID;
protected $currentEffectiveUserPHID;
private $effectiveUser = self::ATTACHABLE;
public function attachEffectiveUser(PhabricatorUser $user) {
$this->effectiveUser = $user;
return $this;
}
public function getEffectiveUser() {
return $this->assertAttached($this->effectiveUser);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,

View file

@ -144,7 +144,7 @@ EOTEXT
->setHeaderText(pht('Builtin and Saved Queries'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($this->newRemarkupDocumentationView($info))
->appendChild($table);
}
@ -223,7 +223,7 @@ EOTEXT
);
if ($constants) {
$constant_lists[] = $this->buildRemarkup(
$constant_lists[] = $this->newRemarkupDocumentationView(
pht(
'Constants supported by the `%s` constraint:',
'statuses'));
@ -283,7 +283,7 @@ EOTEXT
->setHeaderText(pht('Custom Query Constraints'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($this->newRemarkupDocumentationView($info))
->appendChild($table)
->appendChild($constant_lists);
}
@ -391,9 +391,9 @@ EOTEXT
->setHeaderText(pht('Result Ordering'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($orders_info))
->appendChild($this->newRemarkupDocumentationView($orders_info))
->appendChild($orders_table)
->appendChild($this->buildRemarkup($columns_info))
->appendChild($this->newRemarkupDocumentationView($columns_info))
->appendChild($columns_table);
}
@ -472,7 +472,7 @@ EOTEXT
->setHeaderText(pht('Object Fields'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($this->newRemarkupDocumentationView($info))
->appendChild($table);
}
@ -562,7 +562,7 @@ EOTEXT
->setHeaderText(pht('Attachments'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($this->newRemarkupDocumentationView($info))
->appendChild($table);
}
@ -633,21 +633,7 @@ EOTEXT
->setHeaderText(pht('Paging and Limits'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info));
->appendChild($this->newRemarkupDocumentationView($info));
}
private function buildRemarkup($remarkup) {
$viewer = $this->getViewer();
$view = new PHUIRemarkupView($viewer, $remarkup);
$view->setRemarkupOptions(
array(
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false,
));
return id(new PHUIBoxView())
->appendChild($view)
->addPadding(PHUI::PADDING_LARGE);
}
}

View file

@ -0,0 +1,4 @@
<?php
final class PhabricatorDefaultUnlockEngine
extends PhabricatorUnlockEngine {}

View file

@ -0,0 +1,81 @@
<?php
abstract class PhabricatorUnlockEngine
extends Phobject {
final public static function newUnlockEngineForObject($object) {
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
throw new Exception(
pht(
'Object ("%s") does not implement interface "%s", so this type '.
'of object can not be unlocked.',
phutil_describe_type($object),
'PhabricatorApplicationTransactionInterface'));
}
if ($object instanceof PhabricatorUnlockableInterface) {
$engine = $object->newUnlockEngine();
} else {
$engine = new PhabricatorDefaultUnlockEngine();
}
return $engine;
}
public function newUnlockViewTransactions($object, $user) {
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
if (!$this->canApplyTransactionType($object, $type_view)) {
throw new Exception(
pht(
'Object view policy can not be unlocked because this object '.
'does not have a mutable view policy.'));
}
return array(
$this->newTransaction($object)
->setTransactionType($type_view)
->setNewValue($user->getPHID()),
);
}
public function newUnlockEditTransactions($object, $user) {
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
if (!$this->canApplyTransactionType($object, $type_edit)) {
throw new Exception(
pht(
'Object edit policy can not be unlocked because this object '.
'does not have a mutable edit policy.'));
}
return array(
$this->newTransaction($object)
->setTransactionType($type_edit)
->setNewValue($user->getPHID()),
);
}
public function newUnlockOwnerTransactions($object, $user) {
throw new Exception(
pht(
'Object owner can not be unlocked: the unlocking engine ("%s") for '.
'this object does not implement an owner unlocking mechanism.',
get_class($this)));
}
final protected function canApplyTransactionType($object, $type) {
$xaction_types = $object->getApplicationTransactionEditor()
->getTransactionTypesForObject($object);
$xaction_types = array_fuse($xaction_types);
return isset($xaction_types[$type]);
}
final protected function newTransaction($object) {
return $object->getApplicationTransactionTemplate();
}
}

View file

@ -0,0 +1,18 @@
<?php
interface PhabricatorUnlockableInterface {
public function newUnlockEngine();
}
// TEMPLATE IMPLEMENTATION /////////////////////////////////////////////////////
/* -( PhabricatorUnlockableInterface )------------------------------------- */
/*
public function newUnlockEngine() {
return new <<<...>>>UnlockEngine();
}
*/

View file

@ -8,15 +8,58 @@ final class TransactionSearchConduitAPIMethod
}
public function getMethodDescription() {
return pht('Read transactions for an object.');
return pht('Read transactions and comments for an object.');
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodDocumentation() {
$markup = pht(<<<EOREMARKUP
When an object (like a task) is edited, Phabricator creates a "transaction"
and applies it. This list of transactions on each object is the basis for
essentially all edits and comments in Phabricator. Reviewing the transaction
record allows you to see who edited an object, when, and how their edit changed
things.
public function getMethodStatusDescription() {
return pht('This method is new and experimental.');
One common reason to call this method is that you're implmenting a webhook and
just received a notification that an object has changed. See the Webhooks
documentation for more detailed discussion of this use case.
Constraints
===========
These constraints are supported:
- `phids` //Optional list<phid>.// Find specific transactions by PHID. This
is most likely to be useful if you're responding to a webhook notification
and want to inspect only the related events.
- `authorPHIDs` //Optional list<phid>.// Find transactions with particular
authors.
Transaction Format
==================
Each transaction has custom data describing what the transaction did. The
format varies from transaction to transaction. The easiest way to figure out
exactly what a particular transaction looks like is to make the associated kind
of edit to a test object, then query that object.
Not all transactions have data: by default, transactions have a `null` "type"
and no additional data. This API does not expose raw transaction data because
some of it is internal, oddly named, misspelled, confusing, not useful, or
could create security or policy problems to expose directly.
New transactions are exposed (with correctly spelled, comprehensible types and
useful, reasonable fields) as we become aware of use cases for them.
EOREMARKUP
);
$markup = $this->newRemarkupDocumentationView($markup);
return id(new PHUIObjectBoxView())
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeaderText(pht('Method Details'))
->appendChild($markup);
}
protected function defineParamTypes() {
@ -73,24 +116,8 @@ final class TransactionSearchConduitAPIMethod
->setViewer($viewer);
$constraints = $request->getValue('constraints', array());
PhutilTypeSpec::checkMap(
$constraints,
array(
'phids' => 'optional list<string>',
));
$with_phids = idx($constraints, 'phids');
if ($with_phids === array()) {
throw new Exception(
pht(
'Constraint "phids" to "transaction.search" requires nonempty list, '.
'empty list provided.'));
}
if ($with_phids) {
$xaction_query->withPHIDs($with_phids);
}
$xaction_query = $this->applyConstraints($constraints, $xaction_query);
$xactions = $xaction_query->executeWithCursorPager($pager);
@ -218,6 +245,14 @@ final class TransactionSearchConduitAPIMethod
case PhabricatorTransactions::TYPE_CREATE:
$type = 'create';
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($xaction->getMetadataValue('edge:type')) {
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
$type = 'projects';
$fields = $this->newEdgeTransactionFields($xaction);
break;
}
break;
}
}
@ -240,4 +275,69 @@ final class TransactionSearchConduitAPIMethod
return $this->addPagerResults($results, $pager);
}
private function applyConstraints(
array $constraints,
PhabricatorApplicationTransactionQuery $query) {
PhutilTypeSpec::checkMap(
$constraints,
array(
'phids' => 'optional list<string>',
'authorPHIDs' => 'optional list<string>',
));
$with_phids = idx($constraints, 'phids');
if ($with_phids === array()) {
throw new Exception(
pht(
'Constraint "phids" to "transaction.search" requires nonempty list, '.
'empty list provided.'));
}
if ($with_phids) {
$query->withPHIDs($with_phids);
}
$with_authors = idx($constraints, 'authorPHIDs');
if ($with_authors === array()) {
throw new Exception(
pht(
'Constraint "authorPHIDs" to "transaction.search" requires '.
'nonempty list, empty list provided.'));
}
if ($with_authors) {
$query->withAuthorPHIDs($with_authors);
}
return $query;
}
private function newEdgeTransactionFields(
PhabricatorApplicationTransaction $xaction) {
$record = PhabricatorEdgeChangeRecord::newFromTransaction($xaction);
$operations = array();
foreach ($record->getAddedPHIDs() as $phid) {
$operations[] = array(
'operation' => 'add',
'phid' => $phid,
);
}
foreach ($record->getRemovedPHIDs() as $phid) {
$operations[] = array(
'operation' => 'remove',
'phid' => $phid,
);
}
return array(
'operations' => $operations,
);
}
}

View file

@ -3779,9 +3779,14 @@ abstract class PhabricatorApplicationTransactionEditor
$this->mustEncrypt = $adapter->getMustEncryptReasons();
$apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript);
assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction');
$queue_xactions = $adapter->getQueuedTransactions();
return array_merge(
$this->didApplyHeraldRules($object, $adapter, $xscript),
$adapter->getQueuedTransactions());
array_values($apply_xactions),
array_values($queue_xactions));
}
protected function didApplyHeraldRules(

View file

@ -15,6 +15,7 @@ final class PhabricatorWorkerBulkJobEditor
$types = parent::getTransactionTypes();
$types[] = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
$types[] = PhabricatorTransactions::TYPE_EDGE;
return $types;
}

View file

@ -11,23 +11,64 @@ final class PhabricatorWorkerManagementExecuteWorkflow
pht(
'Execute a task explicitly. This command ignores leases, is '.
'dangerous, and may cause work to be performed twice.'))
->setArguments($this->getTaskSelectionArguments());
->setArguments(
array_merge(
array(
array(
'name' => 'retry',
'help' => pht('Retry archived tasks.'),
),
array(
'name' => 'repeat',
'help' => pht('Repeat archived, successful tasks.'),
),
),
$this->getTaskSelectionArguments()));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$tasks = $this->loadTasks($args);
$is_retry = $args->getArg('retry');
$is_repeat = $args->getArg('repeat');
foreach ($tasks as $task) {
$can_execute = !$task->isArchived();
if (!$can_execute) {
$console->writeOut(
if (!$is_retry) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('ARCHIVED'),
pht(
'%s is already archived, and will not be executed. '.
'Use "--retry" to execute archived tasks.',
$this->describeTask($task)));
continue;
}
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
if ($task->getResult() == $result_success) {
if (!$is_repeat) {
$console->writeOut(
"**<bg:yellow> %s </bg>** %s\n",
pht('SUCCEEDED'),
pht(
'%s has already succeeded, and will not be retried. '.
'Use "--repeat" to repeat successful tasks.',
$this->describeTask($task)));
continue;
}
}
echo tsprintf(
"**<bg:yellow> %s </bg>** %s\n",
pht('ARCHIVED'),
pht(
'%s is already archived, and can not be executed.',
'Unarchiving %s.',
$this->describeTask($task)));
continue;
$task = $task->unarchiveTask();
}
// NOTE: This ignores leases, maybe it should respect them without

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