mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-09 14:21:02 +01:00
(stable) Merge 2015 Week 44
This commit is contained in:
commit
686895d9a5
90 changed files with 2329 additions and 502 deletions
22
README.md
22
README.md
|
@ -1,4 +1,4 @@
|
||||||
**Phabricator** is an open source collection of web applications which help software companies build better software.
|
**Phabricator** is a collection of web applications which help software companies build better software.
|
||||||
|
|
||||||
Phabricator includes applications for:
|
Phabricator includes applications for:
|
||||||
|
|
||||||
|
@ -18,25 +18,13 @@ Phabricator is developed and maintained by [Phacility](http://phacility.com).
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
|
||||||
**BUG REPORTS**
|
**SUPPORT RESOURCES**
|
||||||
|
|
||||||
Please update your install to **HEAD** before filing bug reports. Follow our [bug reporting guide](https://secure.phabricator.com/book/phabcontrib/article/bug_reports/) for complete instructions.
|
For resources on filing bugs, requesting features, reporting security issues, and getting other kinds of support, see [Support Resources](https://secure.phabricator.com/book/phabricator/article/support/).
|
||||||
|
|
||||||
**FEATURE REQUESTS**
|
**NO PULL REQUESTS!**
|
||||||
|
|
||||||
We're big fans of feature requests that state core problems, not just 'add this'. We've compiled a short guide to effective upstream requests [here](https://secure.phabricator.com/book/phabcontrib/article/feature_requests/).
|
We do not accept pull requests through GitHub. If you would like to contribute code, please read our [Contributor's Guide](https://secure.phabricator.com/book/phabcontrib/article/contributing_code/).
|
||||||
|
|
||||||
**COMMUNITY CHAT**
|
|
||||||
|
|
||||||
Please visit our [IRC Channel (#phabricator on FreeNode)](irc://chat.freenode.net/phabricator) to talk with other members of the Phabricator community. There might be someone there who can help you with setup issues or what image to choose for a macro.
|
|
||||||
|
|
||||||
**SECURITY ISSUES**
|
|
||||||
|
|
||||||
Phabricator participates in HackerOne and may pay out for various issues reported there. You can find out more information on our [HackerOne page](https://hackerone.com/phabricator).
|
|
||||||
|
|
||||||
**PULL REQUESTS**
|
|
||||||
|
|
||||||
We do not accept pull requests through GitHub. If you would like to contribute code, please read our [Contributor's Guide](https://secure.phabricator.com/book/phabcontrib/article/contributing_code/) for more information.
|
|
||||||
|
|
||||||
**LICENSE**
|
**LICENSE**
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
return array(
|
return array(
|
||||||
'names' => array(
|
'names' => array(
|
||||||
'core.pkg.css' => 'c65b251d',
|
'core.pkg.css' => '15e557bc',
|
||||||
'core.pkg.js' => '47dc9ebb',
|
'core.pkg.js' => '47dc9ebb',
|
||||||
'darkconsole.pkg.js' => 'e7393ebb',
|
'darkconsole.pkg.js' => 'e7393ebb',
|
||||||
'differential.pkg.css' => '2de124c9',
|
'differential.pkg.css' => '2de124c9',
|
||||||
|
@ -18,14 +18,14 @@ return array(
|
||||||
'maniphest.pkg.js' => '3ec6a6d5',
|
'maniphest.pkg.js' => '3ec6a6d5',
|
||||||
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
|
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
|
||||||
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
|
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
|
||||||
'rsrc/css/aphront/dialog-view.css' => 'fe58b18d',
|
'rsrc/css/aphront/dialog-view.css' => 'be0e3a46',
|
||||||
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
|
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
|
||||||
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
||||||
'rsrc/css/aphront/multi-column.css' => 'fd18389d',
|
'rsrc/css/aphront/multi-column.css' => 'fd18389d',
|
||||||
'rsrc/css/aphront/notification.css' => '9c279160',
|
'rsrc/css/aphront/notification.css' => '9c279160',
|
||||||
'rsrc/css/aphront/panel-view.css' => '8427b78d',
|
'rsrc/css/aphront/panel-view.css' => '8427b78d',
|
||||||
'rsrc/css/aphront/phabricator-nav-view.css' => 'a24cb589',
|
'rsrc/css/aphront/phabricator-nav-view.css' => 'a24cb589',
|
||||||
'rsrc/css/aphront/table-view.css' => '63985f5b',
|
'rsrc/css/aphront/table-view.css' => '61543e7a',
|
||||||
'rsrc/css/aphront/tokenizer.css' => '04875312',
|
'rsrc/css/aphront/tokenizer.css' => '04875312',
|
||||||
'rsrc/css/aphront/tooltip.css' => '7672b60f',
|
'rsrc/css/aphront/tooltip.css' => '7672b60f',
|
||||||
'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
|
'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
|
||||||
|
@ -71,7 +71,7 @@ return array(
|
||||||
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
|
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
|
||||||
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
|
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
|
||||||
'rsrc/css/application/flag/flag.css' => '5337623f',
|
'rsrc/css/application/flag/flag.css' => '5337623f',
|
||||||
'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4',
|
'rsrc/css/application/harbormaster/harbormaster.css' => 'b0758ca5',
|
||||||
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
|
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
|
||||||
'rsrc/css/application/herald/herald.css' => '826075fa',
|
'rsrc/css/application/herald/herald.css' => '826075fa',
|
||||||
'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5',
|
'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5',
|
||||||
|
@ -104,7 +104,7 @@ return array(
|
||||||
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
|
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
|
||||||
'rsrc/css/application/uiexample/example.css' => '528b19de',
|
'rsrc/css/application/uiexample/example.css' => '528b19de',
|
||||||
'rsrc/css/core/core.css' => 'a76cefc9',
|
'rsrc/css/core/core.css' => 'a76cefc9',
|
||||||
'rsrc/css/core/remarkup.css' => 'fa3a8225',
|
'rsrc/css/core/remarkup.css' => '8d341238',
|
||||||
'rsrc/css/core/syntax.css' => '9fd11da8',
|
'rsrc/css/core/syntax.css' => '9fd11da8',
|
||||||
'rsrc/css/core/z-index.css' => '57ddcaa2',
|
'rsrc/css/core/z-index.css' => '57ddcaa2',
|
||||||
'rsrc/css/diviner/diviner-shared.css' => '5a337049',
|
'rsrc/css/diviner/diviner-shared.css' => '5a337049',
|
||||||
|
@ -141,7 +141,7 @@ return array(
|
||||||
'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f',
|
'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f',
|
||||||
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
||||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||||
'rsrc/css/phui/phui-property-list-view.css' => '03904f6b',
|
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
|
||||||
'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3',
|
'rsrc/css/phui/phui-remarkup-preview.css' => '867f85b3',
|
||||||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||||
'rsrc/css/phui/phui-status.css' => '888cedb8',
|
'rsrc/css/phui/phui-status.css' => '888cedb8',
|
||||||
|
@ -489,11 +489,11 @@ return array(
|
||||||
'almanac-css' => 'dbb9b3af',
|
'almanac-css' => 'dbb9b3af',
|
||||||
'aphront-bars' => '231ac33c',
|
'aphront-bars' => '231ac33c',
|
||||||
'aphront-dark-console-css' => '6378ef3d',
|
'aphront-dark-console-css' => '6378ef3d',
|
||||||
'aphront-dialog-view-css' => 'fe58b18d',
|
'aphront-dialog-view-css' => 'be0e3a46',
|
||||||
'aphront-list-filter-view-css' => '5d6f0526',
|
'aphront-list-filter-view-css' => '5d6f0526',
|
||||||
'aphront-multi-column-view-css' => 'fd18389d',
|
'aphront-multi-column-view-css' => 'fd18389d',
|
||||||
'aphront-panel-view-css' => '8427b78d',
|
'aphront-panel-view-css' => '8427b78d',
|
||||||
'aphront-table-view-css' => '63985f5b',
|
'aphront-table-view-css' => '61543e7a',
|
||||||
'aphront-tokenizer-control-css' => '04875312',
|
'aphront-tokenizer-control-css' => '04875312',
|
||||||
'aphront-tooltip-css' => '7672b60f',
|
'aphront-tooltip-css' => '7672b60f',
|
||||||
'aphront-typeahead-control-css' => '0e403212',
|
'aphront-typeahead-control-css' => '0e403212',
|
||||||
|
@ -528,7 +528,7 @@ return array(
|
||||||
'font-lato' => '5ab1a46a',
|
'font-lato' => '5ab1a46a',
|
||||||
'font-roboto-slab' => 'f24a53cb',
|
'font-roboto-slab' => 'f24a53cb',
|
||||||
'global-drag-and-drop-css' => '697324ad',
|
'global-drag-and-drop-css' => '697324ad',
|
||||||
'harbormaster-css' => '49d64eb4',
|
'harbormaster-css' => 'b0758ca5',
|
||||||
'herald-css' => '826075fa',
|
'herald-css' => '826075fa',
|
||||||
'herald-rule-editor' => '91a6031b',
|
'herald-rule-editor' => '91a6031b',
|
||||||
'herald-test-css' => 'a52e323e',
|
'herald-test-css' => 'a52e323e',
|
||||||
|
@ -735,7 +735,7 @@ return array(
|
||||||
'phabricator-object-selector-css' => '85ee8ce6',
|
'phabricator-object-selector-css' => '85ee8ce6',
|
||||||
'phabricator-phtize' => 'd254d646',
|
'phabricator-phtize' => 'd254d646',
|
||||||
'phabricator-prefab' => '6920d200',
|
'phabricator-prefab' => '6920d200',
|
||||||
'phabricator-remarkup-css' => 'fa3a8225',
|
'phabricator-remarkup-css' => '8d341238',
|
||||||
'phabricator-search-results-css' => '7dea472c',
|
'phabricator-search-results-css' => '7dea472c',
|
||||||
'phabricator-shaped-request' => '7cbe244b',
|
'phabricator-shaped-request' => '7cbe244b',
|
||||||
'phabricator-side-menu-view-css' => 'bec2458e',
|
'phabricator-side-menu-view-css' => 'bec2458e',
|
||||||
|
@ -792,7 +792,7 @@ return array(
|
||||||
'phui-object-item-list-view-css' => '26c30d3f',
|
'phui-object-item-list-view-css' => '26c30d3f',
|
||||||
'phui-pager-css' => 'bea33d23',
|
'phui-pager-css' => 'bea33d23',
|
||||||
'phui-pinboard-view-css' => '2495140e',
|
'phui-pinboard-view-css' => '2495140e',
|
||||||
'phui-property-list-view-css' => '03904f6b',
|
'phui-property-list-view-css' => '27b2849e',
|
||||||
'phui-remarkup-preview-css' => '867f85b3',
|
'phui-remarkup-preview-css' => '867f85b3',
|
||||||
'phui-spacing-css' => '042804d6',
|
'phui-spacing-css' => '042804d6',
|
||||||
'phui-status-list-view-css' => '888cedb8',
|
'phui-status-list-view-css' => '888cedb8',
|
||||||
|
|
1
resources/sql/autopatches/20150906.mailinglist.sql
Normal file
1
resources/sql/autopatches/20150906.mailinglist.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE {$NAMESPACE}_metamta.metamta_mailinglist;
|
5
resources/sql/autopatches/20151023.harborpolicy.1.sql
Normal file
5
resources/sql/autopatches/20151023.harborpolicy.1.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan
|
||||||
|
ADD viewPolicy VARBINARY(64) NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan
|
||||||
|
ADD editPolicy VARBINARY(64) NOT NULL;
|
21
resources/sql/autopatches/20151023.harborpolicy.2.php
Normal file
21
resources/sql/autopatches/20151023.harborpolicy.2.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$table = new HarbormasterBuildPlan();
|
||||||
|
$conn_w = $table->establishConnection('w');
|
||||||
|
|
||||||
|
$view_policy = PhabricatorPolicies::getMostOpenPolicy();
|
||||||
|
queryfx(
|
||||||
|
$conn_w,
|
||||||
|
'UPDATE %T SET viewPolicy = %s WHERE viewPolicy = %s',
|
||||||
|
$table->getTableName(),
|
||||||
|
$view_policy,
|
||||||
|
'');
|
||||||
|
|
||||||
|
$edit_policy = id(new PhabricatorHarbormasterApplication())
|
||||||
|
->getPolicy(HarbormasterCreatePlansCapability::CAPABILITY);
|
||||||
|
queryfx(
|
||||||
|
$conn_w,
|
||||||
|
'UPDATE %T SET editPolicy = %s WHERE editPolicy = %s',
|
||||||
|
$table->getTableName(),
|
||||||
|
$edit_policy,
|
||||||
|
'');
|
2
resources/sql/autopatches/20151023.patchduration.sql
Normal file
2
resources/sql/autopatches/20151023.patchduration.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_meta_data.patch_status
|
||||||
|
ADD duration BIGINT UNSIGNED;
|
|
@ -416,6 +416,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
|
'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
|
||||||
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
|
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
|
||||||
'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php',
|
'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php',
|
||||||
|
'DifferentialNextStepField' => 'applications/differential/customfield/DifferentialNextStepField.php',
|
||||||
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
|
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
|
||||||
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
|
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
|
||||||
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
||||||
|
@ -709,6 +710,7 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
|
'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
|
||||||
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
|
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
|
||||||
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
||||||
|
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
||||||
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
|
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
|
||||||
'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
|
'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
|
||||||
'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
|
'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
|
||||||
|
@ -909,6 +911,7 @@ phutil_register_library_map(array(
|
||||||
'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php',
|
'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php',
|
||||||
'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php',
|
'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php',
|
||||||
'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php',
|
'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php',
|
||||||
|
'DrydockTestRepositoryOperation' => 'applications/drydock/operation/DrydockTestRepositoryOperation.php',
|
||||||
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
|
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
|
||||||
'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php',
|
'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php',
|
||||||
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
|
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
|
||||||
|
@ -991,6 +994,8 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
|
'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
|
||||||
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
|
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
|
||||||
'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
|
'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
|
||||||
|
'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php',
|
||||||
|
'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php',
|
||||||
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
|
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
|
||||||
'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
|
'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
|
||||||
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
|
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
|
||||||
|
@ -1036,6 +1041,7 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php',
|
'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php',
|
||||||
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
|
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
|
||||||
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
|
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
|
||||||
|
'HarbormasterCreatePlansCapability' => 'applications/harbormaster/capability/HarbormasterCreatePlansCapability.php',
|
||||||
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
|
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
|
||||||
'HarbormasterDrydockBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterDrydockBuildStepGroup.php',
|
'HarbormasterDrydockBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterDrydockBuildStepGroup.php',
|
||||||
'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
|
'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
|
||||||
|
@ -1049,7 +1055,6 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
|
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
|
||||||
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
|
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
|
||||||
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
|
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
|
||||||
'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php',
|
|
||||||
'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
|
'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
|
||||||
'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php',
|
'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php',
|
||||||
'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php',
|
'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php',
|
||||||
|
@ -1076,6 +1081,7 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
|
'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
|
||||||
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
|
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
|
||||||
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
|
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
|
||||||
|
'HarbormasterStepViewController' => 'applications/harbormaster/controller/HarbormasterStepViewController.php',
|
||||||
'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php',
|
'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php',
|
||||||
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
|
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
|
||||||
'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php',
|
'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php',
|
||||||
|
@ -1914,6 +1920,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php',
|
'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php',
|
||||||
'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php',
|
'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php',
|
||||||
'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php',
|
'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php',
|
||||||
|
'PhabricatorConfigVersionsModule' => 'applications/config/module/PhabricatorConfigVersionsModule.php',
|
||||||
'PhabricatorConfigWelcomeController' => 'applications/config/controller/PhabricatorConfigWelcomeController.php',
|
'PhabricatorConfigWelcomeController' => 'applications/config/controller/PhabricatorConfigWelcomeController.php',
|
||||||
'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php',
|
'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php',
|
||||||
'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php',
|
'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php',
|
||||||
|
@ -2403,7 +2410,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php',
|
'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php',
|
||||||
'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php',
|
'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php',
|
||||||
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
|
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
|
||||||
'PhabricatorMetaMTAMailingList' => 'applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php',
|
|
||||||
'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php',
|
'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php',
|
||||||
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
|
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
|
||||||
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
|
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
|
||||||
|
@ -4154,6 +4160,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialLocalCommitsView' => 'AphrontView',
|
'DifferentialLocalCommitsView' => 'AphrontView',
|
||||||
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
|
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
|
||||||
'DifferentialModernHunk' => 'DifferentialHunk',
|
'DifferentialModernHunk' => 'DifferentialHunk',
|
||||||
|
'DifferentialNextStepField' => 'DifferentialCustomField',
|
||||||
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
|
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||||
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||||
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
||||||
|
@ -4461,6 +4468,7 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||||
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
|
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
|
||||||
'DiffusionRepositoryTag' => 'Phobject',
|
'DiffusionRepositoryTag' => 'Phobject',
|
||||||
|
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
||||||
'DiffusionRequest' => 'Phobject',
|
'DiffusionRequest' => 'Phobject',
|
||||||
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||||
'DiffusionResolveUserQuery' => 'Phobject',
|
'DiffusionResolveUserQuery' => 'Phobject',
|
||||||
|
@ -4701,6 +4709,7 @@ phutil_register_library_map(array(
|
||||||
'DrydockSlotLock' => 'DrydockDAO',
|
'DrydockSlotLock' => 'DrydockDAO',
|
||||||
'DrydockSlotLockException' => 'Exception',
|
'DrydockSlotLockException' => 'Exception',
|
||||||
'DrydockSlotLockFailureLogType' => 'DrydockLogType',
|
'DrydockSlotLockFailureLogType' => 'DrydockLogType',
|
||||||
|
'DrydockTestRepositoryOperation' => 'DrydockRepositoryOperationType',
|
||||||
'DrydockWebrootInterface' => 'DrydockInterface',
|
'DrydockWebrootInterface' => 'DrydockInterface',
|
||||||
'DrydockWorker' => 'PhabricatorWorker',
|
'DrydockWorker' => 'PhabricatorWorker',
|
||||||
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||||
|
@ -4815,6 +4824,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorSubscribableInterface',
|
'PhabricatorSubscribableInterface',
|
||||||
),
|
),
|
||||||
'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
|
'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability',
|
||||||
|
'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||||
'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
|
'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
|
'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
|
||||||
'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
@ -4874,6 +4885,7 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod',
|
'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod',
|
||||||
'HarbormasterController' => 'PhabricatorController',
|
'HarbormasterController' => 'PhabricatorController',
|
||||||
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
|
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
|
||||||
|
'HarbormasterCreatePlansCapability' => 'PhabricatorPolicyCapability',
|
||||||
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
||||||
'HarbormasterDrydockBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
'HarbormasterDrydockBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
||||||
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||||
|
@ -4887,7 +4899,6 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||||
'HarbormasterLintMessagesController' => 'HarbormasterController',
|
'HarbormasterLintMessagesController' => 'HarbormasterController',
|
||||||
'HarbormasterLintPropertyView' => 'AphrontView',
|
'HarbormasterLintPropertyView' => 'AphrontView',
|
||||||
'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability',
|
|
||||||
'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
|
'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
|
||||||
'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow',
|
'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow',
|
||||||
'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||||
|
@ -4914,6 +4925,7 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterStepAddController' => 'HarbormasterController',
|
'HarbormasterStepAddController' => 'HarbormasterController',
|
||||||
'HarbormasterStepDeleteController' => 'HarbormasterController',
|
'HarbormasterStepDeleteController' => 'HarbormasterController',
|
||||||
'HarbormasterStepEditController' => 'HarbormasterController',
|
'HarbormasterStepEditController' => 'HarbormasterController',
|
||||||
|
'HarbormasterStepViewController' => 'HarbormasterController',
|
||||||
'HarbormasterTargetEngine' => 'Phobject',
|
'HarbormasterTargetEngine' => 'Phobject',
|
||||||
'HarbormasterTargetWorker' => 'HarbormasterWorker',
|
'HarbormasterTargetWorker' => 'HarbormasterWorker',
|
||||||
'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
||||||
|
@ -5893,6 +5905,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction',
|
'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorConfigValidationException' => 'Exception',
|
'PhabricatorConfigValidationException' => 'Exception',
|
||||||
|
'PhabricatorConfigVersionsModule' => 'PhabricatorConfigModule',
|
||||||
'PhabricatorConfigWelcomeController' => 'PhabricatorConfigController',
|
'PhabricatorConfigWelcomeController' => 'PhabricatorConfigController',
|
||||||
'PhabricatorConpherenceApplication' => 'PhabricatorApplication',
|
'PhabricatorConpherenceApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
|
@ -6456,7 +6469,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
|
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
|
||||||
'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO',
|
|
||||||
'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery',
|
'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery',
|
||||||
'PhabricatorMetaMTAPermanentFailureException' => 'Exception',
|
'PhabricatorMetaMTAPermanentFailureException' => 'Exception',
|
||||||
'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
|
'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
|
||||||
|
|
|
@ -28,7 +28,7 @@ final class AphrontRequest extends Phobject {
|
||||||
private $applicationConfiguration;
|
private $applicationConfiguration;
|
||||||
private $site;
|
private $site;
|
||||||
private $controller;
|
private $controller;
|
||||||
private $uriData;
|
private $uriData = array();
|
||||||
private $cookiePrefix;
|
private $cookiePrefix;
|
||||||
|
|
||||||
public function __construct($host, $path) {
|
public function __construct($host, $path) {
|
||||||
|
|
|
@ -132,10 +132,7 @@ abstract class PhabricatorAphlictManagementWorkflow
|
||||||
|
|
||||||
if (posix_getuid() == 0) {
|
if (posix_getuid() == 0) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht('The notification server should not be run as root.'));
|
||||||
// TODO: Update this message after a while.
|
|
||||||
'The notification server should not be run as root. It no '.
|
|
||||||
'longer requires access to privileged ports.'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we can write to the PID file.
|
// Make sure we can write to the PID file.
|
||||||
|
|
|
@ -84,6 +84,22 @@ final class PhabricatorAuthOneTimeLoginController
|
||||||
->addCancelButton('/login/email/', pht('Send Another Email'));
|
->addCancelButton('/login/email/', pht('Send Another Email'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$target_user->canEstablishWebSessions()) {
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Unable to Establish Web Session'))
|
||||||
|
->setShortTitle(pht('Login Failure'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'You are trying to gain access to an account ("%s") that can not '.
|
||||||
|
'establish a web session.',
|
||||||
|
$target_user->getUsername()))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'Special users like daemons and mailing lists are not permitted '.
|
||||||
|
'to log in via the web. Log in as a normal user instead.'))
|
||||||
|
->addCancelButton('/');
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
// If we have an email bound into this URI, verify email so that clicking
|
// If we have an email bound into this URI, verify email so that clicking
|
||||||
// the link in the "Welcome" email is good enough, without requiring users
|
// the link in the "Welcome" email is good enough, without requiring users
|
||||||
|
|
|
@ -71,6 +71,16 @@ final class PhabricatorAuthManagementRecoverWorkflow
|
||||||
$can_recover));
|
$can_recover));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$user->canEstablishWebSessions()) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'This account ("%s") can not establish web sessions, so it is '.
|
||||||
|
'not possible to generate a functional recovery link. Special '.
|
||||||
|
'accounts like daemons and mailing lists can not log in via the '.
|
||||||
|
'web UI.',
|
||||||
|
$username));
|
||||||
|
}
|
||||||
|
|
||||||
$engine = new PhabricatorAuthSessionEngine();
|
$engine = new PhabricatorAuthSessionEngine();
|
||||||
$onetime_uri = $engine->getOneTimeLoginURI(
|
$onetime_uri = $engine->getOneTimeLoginURI(
|
||||||
$user,
|
$user,
|
||||||
|
|
|
@ -319,9 +319,11 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
|
||||||
->addMySQLConfig('innodb_buffer_pool_size');
|
->addMySQLConfig('innodb_buffer_pool_size');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$conn_w = id(new PhabricatorUser())->establishConnection('w');
|
||||||
|
|
||||||
$ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection(
|
$ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection(
|
||||||
'utf8mb4',
|
'utf8mb4',
|
||||||
id(new PhabricatorUser())->establishConnection('w'));
|
$conn_w);
|
||||||
if (!$ok) {
|
if (!$ok) {
|
||||||
$summary = pht(
|
$summary = pht(
|
||||||
'You are using an old version of MySQL, and should upgrade.');
|
'You are using an old version of MySQL, and should upgrade.');
|
||||||
|
@ -339,6 +341,28 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
|
||||||
->setMessage($message);
|
->setMessage($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$info = queryfx_one(
|
||||||
|
$conn_w,
|
||||||
|
'SELECT UNIX_TIMESTAMP() epoch');
|
||||||
|
|
||||||
|
$epoch = (int)$info['epoch'];
|
||||||
|
$local = PhabricatorTime::getNow();
|
||||||
|
$delta = (int)abs($local - $epoch);
|
||||||
|
if ($delta > 60) {
|
||||||
|
$this->newIssue('mysql.clock')
|
||||||
|
->setName(pht('Major Web/Database Clock Skew'))
|
||||||
|
->setSummary(
|
||||||
|
pht(
|
||||||
|
'This host is set to a very different time than the database.'))
|
||||||
|
->setMessage(
|
||||||
|
pht(
|
||||||
|
'The database host and this host ("%s") disagree on the current '.
|
||||||
|
'time by more than 60 seconds (absolute skew is %s seconds). '.
|
||||||
|
'Check that the current time is set correctly everywhere.',
|
||||||
|
php_uname('n'),
|
||||||
|
new PhutilNumber($delta)));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function shouldUseMySQLSearchEngine() {
|
protected function shouldUseMySQLSearchEngine() {
|
||||||
|
|
|
@ -58,31 +58,10 @@ final class PhabricatorConfigAllController
|
||||||
$panel->setHeaderText(pht('Current Settings'));
|
$panel->setHeaderText(pht('Current Settings'));
|
||||||
$panel->setTable($table);
|
$panel->setTable($table);
|
||||||
|
|
||||||
$versions = $this->loadVersions();
|
|
||||||
|
|
||||||
$version_property_list = id(new PHUIPropertyListView());
|
|
||||||
foreach ($versions as $version) {
|
|
||||||
list($name, $hash) = $version;
|
|
||||||
$version_property_list->addProperty($name, $hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
$object_box = id(new PHUIObjectBoxView())
|
|
||||||
->setHeaderText(pht('Current Version'))
|
|
||||||
->addPropertyList($version_property_list);
|
|
||||||
|
|
||||||
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
|
|
||||||
$version_path = $phabricator_root.'/conf/local/VERSION';
|
|
||||||
if (Filesystem::pathExists($version_path)) {
|
|
||||||
$version_from_file = Filesystem::readFile($version_path);
|
|
||||||
$version_property_list->addProperty(
|
|
||||||
pht('Local Version'),
|
|
||||||
$version_from_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nav = $this->buildSideNavView();
|
$nav = $this->buildSideNavView();
|
||||||
$nav->selectFilter('all/');
|
$nav->selectFilter('all/');
|
||||||
$nav->setCrumbs($crumbs);
|
$nav->setCrumbs($crumbs);
|
||||||
$nav->appendChild($object_box);
|
|
||||||
$nav->appendChild($panel);
|
$nav->appendChild($panel);
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,42 +72,4 @@ final class PhabricatorConfigAllController
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadVersions() {
|
|
||||||
$specs = array(
|
|
||||||
array(
|
|
||||||
'name' => pht('Phabricator Version'),
|
|
||||||
'root' => 'phabricator',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'name' => pht('Arcanist Version'),
|
|
||||||
'root' => 'arcanist',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'name' => pht('libphutil Version'),
|
|
||||||
'root' => 'phutil',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$futures = array();
|
|
||||||
foreach ($specs as $key => $spec) {
|
|
||||||
$root = dirname(phutil_get_library_root($spec['root']));
|
|
||||||
$futures[$key] = id(new ExecFuture('git log --format=%%H -n 1 --'))
|
|
||||||
->setCWD($root);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = array();
|
|
||||||
foreach ($futures as $key => $future) {
|
|
||||||
list($err, $stdout) = $future->resolve();
|
|
||||||
if (!$err) {
|
|
||||||
$name = trim($stdout);
|
|
||||||
} else {
|
|
||||||
$name = pht('Unknown');
|
|
||||||
}
|
|
||||||
$results[$key] = array($specs[$key]['name'], $name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_select_keys($results, array_keys($specs));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,35 @@ final class PhabricatorConfigPHIDModule extends PhabricatorConfigModule {
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach ($types as $key => $type) {
|
foreach ($types as $key => $type) {
|
||||||
|
$class_name = $type->getPHIDTypeApplicationClass();
|
||||||
|
if ($class_name !== null) {
|
||||||
|
$app = PhabricatorApplication::getByClass($class_name);
|
||||||
|
$app_name = $app->getName();
|
||||||
|
|
||||||
|
$icon = $app->getFontIcon();
|
||||||
|
if ($icon) {
|
||||||
|
$app_icon = id(new PHUIIconView())->setIconFont($icon);
|
||||||
|
} else {
|
||||||
|
$app_icon = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$app_name = null;
|
||||||
|
$app_icon = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$icon = $type->getTypeIcon();
|
||||||
|
if ($icon) {
|
||||||
|
$type_icon = id(new PHUIIconView())->setIconFont($icon);
|
||||||
|
} else {
|
||||||
|
$type_icon = null;
|
||||||
|
}
|
||||||
|
|
||||||
$rows[] = array(
|
$rows[] = array(
|
||||||
$type->getTypeConstant(),
|
$type->getTypeConstant(),
|
||||||
get_class($type),
|
get_class($type),
|
||||||
|
$app_icon,
|
||||||
|
$app_name,
|
||||||
|
$type_icon,
|
||||||
$type->getTypeName(),
|
$type->getTypeName(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,12 +56,18 @@ final class PhabricatorConfigPHIDModule extends PhabricatorConfigModule {
|
||||||
array(
|
array(
|
||||||
pht('Constant'),
|
pht('Constant'),
|
||||||
pht('Class'),
|
pht('Class'),
|
||||||
|
null,
|
||||||
|
pht('Application'),
|
||||||
|
null,
|
||||||
pht('Name'),
|
pht('Name'),
|
||||||
))
|
))
|
||||||
->setColumnClasses(
|
->setColumnClasses(
|
||||||
array(
|
array(
|
||||||
null,
|
null,
|
||||||
'pri',
|
'pri',
|
||||||
|
'icon',
|
||||||
|
null,
|
||||||
|
'icon',
|
||||||
'wide',
|
'wide',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorConfigVersionsModule
|
||||||
|
extends PhabricatorConfigModule {
|
||||||
|
|
||||||
|
public function getModuleKey() {
|
||||||
|
return 'versions';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getModuleName() {
|
||||||
|
return pht('Versions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderModuleStatus(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
|
||||||
|
$versions = $this->loadVersions();
|
||||||
|
|
||||||
|
$version_property_list = id(new PHUIPropertyListView());
|
||||||
|
foreach ($versions as $version) {
|
||||||
|
list($name, $hash) = $version;
|
||||||
|
$version_property_list->addProperty($name, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
$object_box = id(new PHUIObjectBoxView())
|
||||||
|
->setHeaderText(pht('Current Versions'))
|
||||||
|
->addPropertyList($version_property_list);
|
||||||
|
|
||||||
|
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
|
||||||
|
$version_path = $phabricator_root.'/conf/local/VERSION';
|
||||||
|
if (Filesystem::pathExists($version_path)) {
|
||||||
|
$version_from_file = Filesystem::readFile($version_path);
|
||||||
|
$version_property_list->addProperty(
|
||||||
|
pht('Local Version'),
|
||||||
|
$version_from_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $object_box;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadVersions() {
|
||||||
|
$specs = array(
|
||||||
|
array(
|
||||||
|
'name' => pht('Phabricator Version'),
|
||||||
|
'root' => 'phabricator',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => pht('Arcanist Version'),
|
||||||
|
'root' => 'arcanist',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => pht('libphutil Version'),
|
||||||
|
'root' => 'phutil',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$futures = array();
|
||||||
|
foreach ($specs as $key => $spec) {
|
||||||
|
$root = dirname(phutil_get_library_root($spec['root']));
|
||||||
|
$futures[$key] = id(new ExecFuture('git log --format=%%H -n 1 --'))
|
||||||
|
->setCWD($root);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
foreach ($futures as $key => $future) {
|
||||||
|
list($err, $stdout) = $future->resolve();
|
||||||
|
if (!$err) {
|
||||||
|
$name = trim($stdout);
|
||||||
|
} else {
|
||||||
|
$name = pht('Unknown');
|
||||||
|
}
|
||||||
|
$results[$key] = array($specs[$key]['name'], $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_select_keys($results, array_keys($specs));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ final class PhabricatorDifferentialConfigOptions
|
||||||
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
|
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
|
||||||
|
|
||||||
$fields = array(
|
$fields = array(
|
||||||
|
new DifferentialNextStepField(),
|
||||||
|
|
||||||
new DifferentialTitleField(),
|
new DifferentialTitleField(),
|
||||||
new DifferentialSummaryField(),
|
new DifferentialSummaryField(),
|
||||||
new DifferentialTestPlanField(),
|
new DifferentialTestPlanField(),
|
||||||
|
|
|
@ -18,44 +18,13 @@ final class DifferentialRevisionOperationController
|
||||||
|
|
||||||
$detail_uri = "/D{$id}";
|
$detail_uri = "/D{$id}";
|
||||||
|
|
||||||
$repository = $revision->getRepository();
|
$op = new DrydockLandRepositoryOperation();
|
||||||
if (!$repository) {
|
$barrier = $op->getBarrierToLanding($viewer, $revision);
|
||||||
return $this->rejectOperation(
|
if ($barrier) {
|
||||||
$revision,
|
return $this->newDialog()
|
||||||
pht('No Repository'),
|
->setTitle($barrier['title'])
|
||||||
pht(
|
->appendParagraph($barrier['body'])
|
||||||
'This revision is not associated with a known repository. Only '.
|
->addCancelButton($detail_uri);
|
||||||
'revisions associated with a tracked repository can be landed '.
|
|
||||||
'automatically.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$repository->canPerformAutomation()) {
|
|
||||||
return $this->rejectOperation(
|
|
||||||
$revision,
|
|
||||||
pht('No Repository Automation'),
|
|
||||||
pht(
|
|
||||||
'The repository this revision is associated with ("%s") is not '.
|
|
||||||
'configured to support automation. Configure automation for the '.
|
|
||||||
'repository to enable revisions to be landed automatically.',
|
|
||||||
$repository->getMonogram()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: At some point we should allow installs to give "land reviewed
|
|
||||||
// code" permission to more users than "push any commit", because it is
|
|
||||||
// a much less powerful operation. For now, just require push so this
|
|
||||||
// doesn't do anything users can't do on their own.
|
|
||||||
$can_push = PhabricatorPolicyFilter::hasCapability(
|
|
||||||
$viewer,
|
|
||||||
$repository,
|
|
||||||
DiffusionPushCapability::CAPABILITY);
|
|
||||||
if (!$can_push) {
|
|
||||||
return $this->rejectOperation(
|
|
||||||
$revision,
|
|
||||||
pht('Unable to Push'),
|
|
||||||
pht(
|
|
||||||
'You do not have permission to push to the repository this '.
|
|
||||||
'revision is associated with ("%s"), so you can not land it.',
|
|
||||||
$repository->getMonogram()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
|
@ -64,8 +33,7 @@ final class DifferentialRevisionOperationController
|
||||||
// occurs.
|
// occurs.
|
||||||
|
|
||||||
$diff = $revision->getActiveDiff();
|
$diff = $revision->getActiveDiff();
|
||||||
|
$repository = $revision->getRepository();
|
||||||
$op = new DrydockLandRepositoryOperation();
|
|
||||||
|
|
||||||
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
|
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
|
||||||
->setAuthorPHID($viewer->getPHID())
|
->setAuthorPHID($viewer->getPHID())
|
||||||
|
@ -96,18 +64,4 @@ final class DifferentialRevisionOperationController
|
||||||
->addSubmitButton(pht('Mutate Repository Unpredictably'));
|
->addSubmitButton(pht('Mutate Repository Unpredictably'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function rejectOperation(
|
|
||||||
DifferentialRevision $revision,
|
|
||||||
$title,
|
|
||||||
$body) {
|
|
||||||
|
|
||||||
$id = $revision->getID();
|
|
||||||
$detail_uri = "/D{$id}";
|
|
||||||
|
|
||||||
return $this->newDialog()
|
|
||||||
->setTitle($title)
|
|
||||||
->appendParagraph($body)
|
|
||||||
->addCancelButton($detail_uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1047,18 +1047,31 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
||||||
$operations = id(new DrydockRepositoryOperationQuery())
|
$operations = id(new DrydockRepositoryOperationQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withObjectPHIDs(array($revision->getPHID()))
|
->withObjectPHIDs(array($revision->getPHID()))
|
||||||
->withOperationStates(
|
->withOperationTypes(
|
||||||
array(
|
array(
|
||||||
DrydockRepositoryOperation::STATE_WAIT,
|
DrydockLandRepositoryOperation::OPCONST,
|
||||||
DrydockRepositoryOperation::STATE_WORK,
|
|
||||||
DrydockRepositoryOperation::STATE_FAIL,
|
|
||||||
))
|
))
|
||||||
->execute();
|
->execute();
|
||||||
if (!$operations) {
|
if (!$operations) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$operation = head(msort($operations, 'getID'));
|
$state_fail = DrydockRepositoryOperation::STATE_FAIL;
|
||||||
|
|
||||||
|
// We're going to show the oldest operation which hasn't failed, or the
|
||||||
|
// most recent failure if they're all failures.
|
||||||
|
$operations = msort($operations, 'getID');
|
||||||
|
foreach ($operations as $operation) {
|
||||||
|
if ($operation->getOperationState() != $state_fail) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found a completed operation, don't render anything. We don't want
|
||||||
|
// to show an older error after the thing worked properly.
|
||||||
|
if ($operation->isDone()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$box_view = id(new PHUIObjectBoxView())
|
$box_view = id(new PHUIObjectBoxView())
|
||||||
->setHeaderText(pht('Active Operations'));
|
->setHeaderText(pht('Active Operations'));
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DifferentialNextStepField
|
||||||
|
extends DifferentialCustomField {
|
||||||
|
|
||||||
|
public function getFieldKey() {
|
||||||
|
return 'differential:next-step';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFieldName() {
|
||||||
|
return pht('Next Step');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFieldDescription() {
|
||||||
|
return pht('Provides a hint for the next step to take.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldAppearInPropertyView() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderPropertyViewLabel() {
|
||||||
|
return $this->getFieldName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderPropertyViewValue(array $handles) {
|
||||||
|
$revision = $this->getObject();
|
||||||
|
$diff = $revision->getActiveDiff();
|
||||||
|
|
||||||
|
$status = $revision->getStatus();
|
||||||
|
if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$local_vcs = $diff->getSourceControlSystem();
|
||||||
|
switch ($local_vcs) {
|
||||||
|
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||||
|
$bookmark = $diff->getBookmark();
|
||||||
|
if (strlen($bookmark)) {
|
||||||
|
$next_step = csprintf('arc land %R', $bookmark);
|
||||||
|
} else {
|
||||||
|
$next_step = csprintf('arc land');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||||
|
$branch = $diff->getBranch();
|
||||||
|
if (strlen($branch)) {
|
||||||
|
$next_step = csprintf('arc land %R', $branch);
|
||||||
|
} else {
|
||||||
|
$next_step = csprintf('arc land');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||||
|
$next_step = csprintf('arc commit');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$next_step = phutil_tag('tt', array(), (string)$next_step);
|
||||||
|
|
||||||
|
return $next_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,7 +26,9 @@ final class DifferentialLandingActionMenuEventListener
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderRevisionAction(PhutilEvent $event) {
|
private function renderRevisionAction(PhutilEvent $event) {
|
||||||
if (!$this->canUseApplication($event->getUser())) {
|
$viewer = $event->getUser();
|
||||||
|
|
||||||
|
if (!$this->canUseApplication($viewer)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +42,22 @@ final class DifferentialLandingActionMenuEventListener
|
||||||
if ($repository->canPerformAutomation()) {
|
if ($repository->canPerformAutomation()) {
|
||||||
$revision_id = $revision->getID();
|
$revision_id = $revision->getID();
|
||||||
|
|
||||||
|
$op = new DrydockLandRepositoryOperation();
|
||||||
|
$barrier = $op->getBarrierToLanding($viewer, $revision);
|
||||||
|
|
||||||
|
if ($barrier) {
|
||||||
|
$can_land = false;
|
||||||
|
} else {
|
||||||
|
$can_land = true;
|
||||||
|
}
|
||||||
|
|
||||||
$action = id(new PhabricatorActionView())
|
$action = id(new PhabricatorActionView())
|
||||||
->setWorkflow(true)
|
|
||||||
->setName(pht('Land Revision'))
|
->setName(pht('Land Revision'))
|
||||||
->setIcon('fa-fighter-jet')
|
->setIcon('fa-fighter-jet')
|
||||||
->setHref("/differential/revision/operation/{$revision_id}/");
|
->setHref("/differential/revision/operation/{$revision_id}/")
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_land);
|
||||||
|
|
||||||
|
|
||||||
$this->addActionMenuItems($event, $action);
|
$this->addActionMenuItems($event, $action);
|
||||||
}
|
}
|
||||||
|
@ -54,7 +67,6 @@ final class DifferentialLandingActionMenuEventListener
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
foreach ($strategies as $strategy) {
|
foreach ($strategies as $strategy) {
|
||||||
$viewer = $event->getUser();
|
|
||||||
$action = $strategy->createMenuItem($viewer, $revision, $repository);
|
$action = $strategy->createMenuItem($viewer, $revision, $repository);
|
||||||
if ($action == null) {
|
if ($action == null) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -73,36 +73,6 @@ final class DifferentialRevisionDetailView extends AphrontView {
|
||||||
->setUser($user)
|
->setUser($user)
|
||||||
->setObject($revision);
|
->setObject($revision);
|
||||||
|
|
||||||
$status = $revision->getStatus();
|
|
||||||
$local_vcs = $this->getDiff()->getSourceControlSystem();
|
|
||||||
|
|
||||||
$next_step = null;
|
|
||||||
if ($status == ArcanistDifferentialRevisionStatus::ACCEPTED) {
|
|
||||||
switch ($local_vcs) {
|
|
||||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
|
||||||
$bookmark = $this->getDiff()->getBookmark();
|
|
||||||
$next_step = ($bookmark != ''
|
|
||||||
? csprintf('arc land %s', $bookmark)
|
|
||||||
: 'arc land');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
|
||||||
$branch = $this->getDiff()->getBranch();
|
|
||||||
$next_step = ($branch != ''
|
|
||||||
? csprintf('arc land %s', $branch)
|
|
||||||
: 'arc land');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
|
||||||
$next_step = 'arc commit';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($next_step) {
|
|
||||||
$next_step = phutil_tag('tt', array(), $next_step);
|
|
||||||
$properties->addProperty(pht('Next Step'), $next_step);
|
|
||||||
}
|
|
||||||
|
|
||||||
$properties->setHasKeyboardShortcuts(true);
|
$properties->setHasKeyboardShortcuts(true);
|
||||||
$properties->setActionList($actions);
|
$properties->setActionList($actions);
|
||||||
$this->setActionList($actions);
|
$this->setActionList($actions);
|
||||||
|
|
|
@ -103,6 +103,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
||||||
'symbol/' => 'DiffusionRepositorySymbolsController',
|
'symbol/' => 'DiffusionRepositorySymbolsController',
|
||||||
'staging/' => 'DiffusionRepositoryEditStagingController',
|
'staging/' => 'DiffusionRepositoryEditStagingController',
|
||||||
'automation/' => 'DiffusionRepositoryEditAutomationController',
|
'automation/' => 'DiffusionRepositoryEditAutomationController',
|
||||||
|
'testautomation/' => 'DiffusionRepositoryTestAutomationController',
|
||||||
),
|
),
|
||||||
'pathtree/(?P<dblob>.*)' => 'DiffusionPathTreeController',
|
'pathtree/(?P<dblob>.*)' => 'DiffusionPathTreeController',
|
||||||
'mirror/' => array(
|
'mirror/' => array(
|
||||||
|
|
|
@ -4,7 +4,7 @@ final class DiffusionRepositoryEditAutomationController
|
||||||
extends DiffusionRepositoryEditController {
|
extends DiffusionRepositoryEditController {
|
||||||
|
|
||||||
protected function processDiffusionRequest(AphrontRequest $request) {
|
protected function processDiffusionRequest(AphrontRequest $request) {
|
||||||
$viewer = $request->getUser();
|
$viewer = $this->getViewer();
|
||||||
$drequest = $this->diffusionRequest;
|
$drequest = $this->diffusionRequest;
|
||||||
$repository = $drequest->getRepository();
|
$repository = $drequest->getRepository();
|
||||||
|
|
||||||
|
|
|
@ -688,6 +688,19 @@ final class DiffusionRepositoryEditMainController
|
||||||
$this->getRepositoryControllerURI($repository, 'edit/automation/'));
|
$this->getRepositoryControllerURI($repository, 'edit/automation/'));
|
||||||
$view->addAction($edit);
|
$view->addAction($edit);
|
||||||
|
|
||||||
|
$can_test = $repository->canPerformAutomation();
|
||||||
|
|
||||||
|
$test = id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-gamepad')
|
||||||
|
->setName(pht('Test Configuration'))
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_test)
|
||||||
|
->setHref(
|
||||||
|
$this->getRepositoryControllerURI(
|
||||||
|
$repository,
|
||||||
|
'edit/testautomation/'));
|
||||||
|
$view->addAction($test);
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryTestAutomationController
|
||||||
|
extends DiffusionRepositoryEditController {
|
||||||
|
|
||||||
|
protected function processDiffusionRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$drequest = $this->diffusionRequest;
|
||||||
|
$repository = $drequest->getRepository();
|
||||||
|
|
||||||
|
$repository = id(new PhabricatorRepositoryQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->withIDs(array($repository->getID()))
|
||||||
|
->executeOne();
|
||||||
|
if (!$repository) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
|
||||||
|
|
||||||
|
if (!$repository->canPerformAutomation()) {
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Automation Not Configured'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'You can not run a configuration test for this repository '.
|
||||||
|
'because you have not configured repository automation yet. '.
|
||||||
|
'Configure it first, then test the configuration.'))
|
||||||
|
->addCancelButton($edit_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$op = new DrydockTestRepositoryOperation();
|
||||||
|
|
||||||
|
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
|
||||||
|
->setAuthorPHID($viewer->getPHID())
|
||||||
|
->setObjectPHID($repository->getPHID())
|
||||||
|
->setRepositoryPHID($repository->getPHID())
|
||||||
|
->setRepositoryTarget('none:')
|
||||||
|
->save();
|
||||||
|
|
||||||
|
$operation->scheduleUpdate();
|
||||||
|
|
||||||
|
$operation_id = $operation->getID();
|
||||||
|
$operation_uri = "/drydock/operation/{$operation_id}/";
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI($operation_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Test Automation Configuration'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'This configuration test will build a working copy of the '.
|
||||||
|
'repository and perform some basic validation. If it works, '.
|
||||||
|
'your configuration is substantially correct.'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'The test will not perform any writes against the repository, so '.
|
||||||
|
'write operations may still fail even if the test passes. This '.
|
||||||
|
'test covers building and reading working copies, but not writing '.
|
||||||
|
'to them.'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'If you run into write failures despite passing this test, '.
|
||||||
|
'it suggests that your setup is nearly correct but authentication '.
|
||||||
|
'is probably not fully configured.'))
|
||||||
|
->addCancelButton($edit_uri)
|
||||||
|
->addSubmitButton(pht('Start Test'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -72,6 +72,7 @@ final class DiffusionCommitQuery
|
||||||
*/
|
*/
|
||||||
public function withRepositoryPHIDs(array $phids) {
|
public function withRepositoryPHIDs(array $phids) {
|
||||||
$this->repositoryPHIDs = $phids;
|
$this->repositoryPHIDs = $phids;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -109,11 +109,6 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
|
||||||
DrydockBlueprint $blueprint,
|
DrydockBlueprint $blueprint,
|
||||||
DrydockResource $resource,
|
DrydockResource $resource,
|
||||||
DrydockLease $lease) {
|
DrydockLease $lease) {
|
||||||
|
|
||||||
if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +119,6 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
|
||||||
|
|
||||||
$lease
|
$lease
|
||||||
->setActivateWhenAcquired(true)
|
->setActivateWhenAcquired(true)
|
||||||
->needSlotLock($this->getLeaseSlotLock($resource))
|
|
||||||
->acquireOnResource($resource);
|
->acquireOnResource($resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,11 +140,6 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getLeaseSlotLock(DrydockResource $resource) {
|
|
||||||
$resource_phid = $resource->getPHID();
|
|
||||||
return "almanac.host.lease({$resource_phid})";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getType() {
|
public function getType() {
|
||||||
return 'host';
|
return 'host';
|
||||||
}
|
}
|
||||||
|
@ -188,7 +177,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFieldSpecifications() {
|
protected function getCustomFieldSpecifications() {
|
||||||
return array(
|
return array(
|
||||||
'almanacServicePHIDs' => array(
|
'almanacServicePHIDs' => array(
|
||||||
'name' => pht('Almanac Services'),
|
'name' => pht('Almanac Services'),
|
||||||
|
@ -207,7 +196,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
|
||||||
'credential.type' =>
|
'credential.type' =>
|
||||||
PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,
|
PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,
|
||||||
),
|
),
|
||||||
) + parent::getFieldSpecifications();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadServices(DrydockBlueprint $blueprint) {
|
private function loadServices(DrydockBlueprint $blueprint) {
|
||||||
|
|
|
@ -16,6 +16,26 @@ abstract class DrydockBlueprintImplementation extends Phobject {
|
||||||
abstract public function getDescription();
|
abstract public function getDescription();
|
||||||
|
|
||||||
public function getFieldSpecifications() {
|
public function getFieldSpecifications() {
|
||||||
|
$fields = array();
|
||||||
|
|
||||||
|
$fields += $this->getCustomFieldSpecifications();
|
||||||
|
|
||||||
|
if ($this->shouldUseConcurrentResourceLimit()) {
|
||||||
|
$fields += array(
|
||||||
|
'allocator.limit' => array(
|
||||||
|
'name' => pht('Limit'),
|
||||||
|
'caption' => pht(
|
||||||
|
'Maximum number of resources this blueprint can have active '.
|
||||||
|
'concurrently.'),
|
||||||
|
'type' => 'int',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCustomFieldSpecifications() {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +336,85 @@ abstract class DrydockBlueprintImplementation extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this implementation use concurrent resource limits?
|
||||||
|
*
|
||||||
|
* Implementations can override this method to opt into standard limit
|
||||||
|
* behavior, which provides a simple concurrent resource limit.
|
||||||
|
*
|
||||||
|
* @return bool True to use limits.
|
||||||
|
*/
|
||||||
|
protected function shouldUseConcurrentResourceLimit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the effective concurrent resource limit for this blueprint.
|
||||||
|
*
|
||||||
|
* @param DrydockBlueprint Blueprint to get the limit for.
|
||||||
|
* @return int|null Limit, or `null` for no limit.
|
||||||
|
*/
|
||||||
|
protected function getConcurrentResourceLimit(DrydockBlueprint $blueprint) {
|
||||||
|
if ($this->shouldUseConcurrentResourceLimit()) {
|
||||||
|
$limit = $blueprint->getFieldValue('allocator.limit');
|
||||||
|
$limit = (int)$limit;
|
||||||
|
if ($limit > 0) {
|
||||||
|
return $limit;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getConcurrentResourceLimitSlotLock(
|
||||||
|
DrydockBlueprint $blueprint) {
|
||||||
|
|
||||||
|
$limit = $this->getConcurrentResourceLimit($blueprint);
|
||||||
|
if ($limit === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blueprint_phid = $blueprint->getPHID();
|
||||||
|
|
||||||
|
// TODO: This logic shouldn't do anything awful, but is a little silly. It
|
||||||
|
// would be nice to unify the "huge limit" and "small limit" cases
|
||||||
|
// eventually but it's a little tricky.
|
||||||
|
|
||||||
|
// If the limit is huge, just pick a random slot. This is just stopping
|
||||||
|
// us from exploding if someone types a billion zillion into the box.
|
||||||
|
if ($limit > 1024) {
|
||||||
|
$slot = mt_rand(0, $limit - 1);
|
||||||
|
return "allocator({$blueprint_phid}).limit({$slot})";
|
||||||
|
}
|
||||||
|
|
||||||
|
// For reasonable limits, actually check for an available slot.
|
||||||
|
$locks = DrydockSlotLock::loadLocks($blueprint_phid);
|
||||||
|
$locks = mpull($locks, null, 'getLockKey');
|
||||||
|
|
||||||
|
$slots = range(0, $limit - 1);
|
||||||
|
shuffle($slots);
|
||||||
|
|
||||||
|
foreach ($slots as $slot) {
|
||||||
|
$slot_lock = "allocator({$blueprint_phid}).limit({$slot})";
|
||||||
|
if (empty($locks[$slot_lock])) {
|
||||||
|
return $slot_lock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found no free slot, just return whatever we checked last (which
|
||||||
|
// is just a random slot). There's a small chance we'll get lucky and the
|
||||||
|
// lock will be free by the time we try to take it, but usually we'll just
|
||||||
|
// fail to grab the lock, throw an appropriate lock exception, and get back
|
||||||
|
// on the right path to retry later.
|
||||||
|
return $slot_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply standard limits on resource allocation rate.
|
* Apply standard limits on resource allocation rate.
|
||||||
*
|
*
|
||||||
|
@ -329,7 +428,7 @@ abstract class DrydockBlueprintImplementation extends Phobject {
|
||||||
// configurable by the blueprint implementation.
|
// configurable by the blueprint implementation.
|
||||||
|
|
||||||
// Limit on total number of active resources.
|
// Limit on total number of active resources.
|
||||||
$total_limit = 1;
|
$total_limit = $this->getConcurrentResourceLimit($blueprint);
|
||||||
|
|
||||||
// Always allow at least this many allocations to be in flight at once.
|
// Always allow at least this many allocations to be in flight at once.
|
||||||
$min_allowed = 1;
|
$min_allowed = 1;
|
||||||
|
@ -358,10 +457,12 @@ abstract class DrydockBlueprintImplementation extends Phobject {
|
||||||
|
|
||||||
// If we're at the limit on total active resources, limit additional
|
// If we're at the limit on total active resources, limit additional
|
||||||
// allocations.
|
// allocations.
|
||||||
|
if ($total_limit !== null) {
|
||||||
$n_total = ($n_alloc + $n_active + $n_broken + $n_released);
|
$n_total = ($n_alloc + $n_active + $n_broken + $n_released);
|
||||||
if ($n_total >= $total_limit) {
|
if ($n_total >= $total_limit) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the number of in-flight allocations is fewer than the minimum number
|
// If the number of in-flight allocations is fewer than the minimum number
|
||||||
// of allowed allocations, don't impose a limit.
|
// of allowed allocations, don't impose a limit.
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
final class DrydockWorkingCopyBlueprintImplementation
|
final class DrydockWorkingCopyBlueprintImplementation
|
||||||
extends DrydockBlueprintImplementation {
|
extends DrydockBlueprintImplementation {
|
||||||
|
|
||||||
|
const PHASE_SQUASHMERGE = 'squashmerge';
|
||||||
|
|
||||||
public function isEnabled() {
|
public function isEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -125,15 +127,7 @@ final class DrydockWorkingCopyBlueprintImplementation
|
||||||
->setOwnerPHID($resource_phid)
|
->setOwnerPHID($resource_phid)
|
||||||
->setAttribute('workingcopy.resourcePHID', $resource_phid)
|
->setAttribute('workingcopy.resourcePHID', $resource_phid)
|
||||||
->setAllowedBlueprintPHIDs($blueprint_phids);
|
->setAllowedBlueprintPHIDs($blueprint_phids);
|
||||||
|
$resource->setAttribute('host.leasePHID', $host_lease->getPHID());
|
||||||
$resource
|
|
||||||
->setAttribute('host.leasePHID', $host_lease->getPHID())
|
|
||||||
->save();
|
|
||||||
|
|
||||||
$host_lease->queueForActivation();
|
|
||||||
|
|
||||||
// TODO: Add some limits to the number of working copies we can have at
|
|
||||||
// once?
|
|
||||||
|
|
||||||
$map = $lease->getAttribute('repositories.map');
|
$map = $lease->getAttribute('repositories.map');
|
||||||
foreach ($map as $key => $value) {
|
foreach ($map as $key => $value) {
|
||||||
|
@ -143,10 +137,18 @@ final class DrydockWorkingCopyBlueprintImplementation
|
||||||
'phid',
|
'phid',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
$resource->setAttribute('repositories.map', $map);
|
||||||
|
|
||||||
return $resource
|
$slot_lock = $this->getConcurrentResourceLimitSlotLock($blueprint);
|
||||||
->setAttribute('repositories.map', $map)
|
if ($slot_lock !== null) {
|
||||||
->allocateResource();
|
$resource->needSlotLock($slot_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resource->allocateResource();
|
||||||
|
|
||||||
|
$host_lease->queueForActivation();
|
||||||
|
|
||||||
|
return $resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function activateResource(
|
public function activateResource(
|
||||||
|
@ -237,8 +239,7 @@ final class DrydockWorkingCopyBlueprintImplementation
|
||||||
$cmd = array();
|
$cmd = array();
|
||||||
$arg = array();
|
$arg = array();
|
||||||
|
|
||||||
$cmd[] = 'cd %s';
|
$interface->pushWorkingDirectory("{$root}/repo/{$directory}/");
|
||||||
$arg[] = "{$root}/repo/{$directory}/";
|
|
||||||
|
|
||||||
$cmd[] = 'git clean -d --force';
|
$cmd[] = 'git clean -d --force';
|
||||||
$cmd[] = 'git fetch';
|
$cmd[] = 'git fetch';
|
||||||
|
@ -285,6 +286,15 @@ final class DrydockWorkingCopyBlueprintImplementation
|
||||||
if (idx($spec, 'default')) {
|
if (idx($spec, 'default')) {
|
||||||
$default = $directory;
|
$default = $directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$merges = idx($spec, 'merges');
|
||||||
|
if ($merges) {
|
||||||
|
foreach ($merges as $merge) {
|
||||||
|
$this->applyMerge($lease, $interface, $merge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$interface->popWorkingDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($default === null) {
|
if ($default === null) {
|
||||||
|
@ -333,7 +343,7 @@ final class DrydockWorkingCopyBlueprintImplementation
|
||||||
$command_interface = $host_lease->getInterface($type);
|
$command_interface = $host_lease->getInterface($type);
|
||||||
|
|
||||||
$path = $lease->getAttribute('workingcopy.default');
|
$path = $lease->getAttribute('workingcopy.default');
|
||||||
$command_interface->setWorkingDirectory($path);
|
$command_interface->pushWorkingDirectory($path);
|
||||||
|
|
||||||
return $command_interface;
|
return $command_interface;
|
||||||
}
|
}
|
||||||
|
@ -393,15 +403,87 @@ final class DrydockWorkingCopyBlueprintImplementation
|
||||||
return $lease;
|
return $lease;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFieldSpecifications() {
|
protected function getCustomFieldSpecifications() {
|
||||||
return array(
|
return array(
|
||||||
'blueprintPHIDs' => array(
|
'blueprintPHIDs' => array(
|
||||||
'name' => pht('Use Blueprints'),
|
'name' => pht('Use Blueprints'),
|
||||||
'type' => 'blueprints',
|
'type' => 'blueprints',
|
||||||
'required' => true,
|
'required' => true,
|
||||||
),
|
),
|
||||||
) + parent::getFieldSpecifications();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function shouldUseConcurrentResourceLimit() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyMerge(
|
||||||
|
DrydockLease $lease,
|
||||||
|
DrydockCommandInterface $interface,
|
||||||
|
array $merge) {
|
||||||
|
|
||||||
|
$src_uri = $merge['src.uri'];
|
||||||
|
$src_ref = $merge['src.ref'];
|
||||||
|
|
||||||
|
$interface->execx(
|
||||||
|
'git fetch --no-tags -- %s +%s:%s',
|
||||||
|
$src_uri,
|
||||||
|
$src_ref,
|
||||||
|
$src_ref);
|
||||||
|
|
||||||
|
// NOTE: This can never actually generate a commit because we pass
|
||||||
|
// "--squash", but git sometimes runs code to check that a username and
|
||||||
|
// email are configured anyway.
|
||||||
|
$real_command = csprintf(
|
||||||
|
'git -c user.name=%s -c user.email=%s merge --no-stat --squash -- %R',
|
||||||
|
'drydock',
|
||||||
|
'drydock@phabricator',
|
||||||
|
$src_ref);
|
||||||
|
|
||||||
|
// Show the user a simplified command if the operation fails and we need to
|
||||||
|
// report an error.
|
||||||
|
$show_command = csprintf(
|
||||||
|
'git merge --squash -- %R',
|
||||||
|
$src_ref);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$interface->execx('%C', $real_command);
|
||||||
|
} catch (CommandException $ex) {
|
||||||
|
$this->setWorkingCopyVCSErrorFromCommandException(
|
||||||
|
$lease,
|
||||||
|
self::PHASE_SQUASHMERGE,
|
||||||
|
$show_command,
|
||||||
|
$ex);
|
||||||
|
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setWorkingCopyVCSErrorFromCommandException(
|
||||||
|
DrydockLease $lease,
|
||||||
|
$phase,
|
||||||
|
$command,
|
||||||
|
CommandException $ex) {
|
||||||
|
|
||||||
|
$error = array(
|
||||||
|
'phase' => $phase,
|
||||||
|
'command' => (string)$command,
|
||||||
|
'raw' => (string)$ex->getCommand(),
|
||||||
|
'err' => $ex->getError(),
|
||||||
|
'stdout' => $ex->getStdout(),
|
||||||
|
'stderr' => $ex->getStderr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$lease->setAttribute('workingcopy.vcs.error', $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWorkingCopyVCSError(DrydockLease $lease) {
|
||||||
|
$error = $lease->getAttribute('workingcopy.vcs.error');
|
||||||
|
if (!$error) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,15 @@ final class DrydockRepositoryOperationViewController
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
->addPropertyList($properties);
|
->addPropertyList($properties);
|
||||||
|
|
||||||
|
$status_view = id(new DrydockRepositoryOperationStatusView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setOperation($operation);
|
||||||
|
|
||||||
return $this->buildApplicationPage(
|
return $this->buildApplicationPage(
|
||||||
array(
|
array(
|
||||||
$crumbs,
|
$crumbs,
|
||||||
$object_box,
|
$object_box,
|
||||||
|
$status_view,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
|
@ -83,6 +88,15 @@ final class DrydockRepositoryOperationViewController
|
||||||
pht('Object'),
|
pht('Object'),
|
||||||
$viewer->renderHandle($operation->getObjectPHID()));
|
$viewer->renderHandle($operation->getObjectPHID()));
|
||||||
|
|
||||||
|
$lease_phid = $operation->getWorkingCopyLeasePHID();
|
||||||
|
if ($lease_phid) {
|
||||||
|
$lease_display = $viewer->renderHandle($lease_phid);
|
||||||
|
} else {
|
||||||
|
$lease_display = phutil_tag('em', array(), pht('None'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->addProperty(pht('Working Copy'), $lease_display);
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,27 @@ abstract class DrydockCommandInterface extends DrydockInterface {
|
||||||
|
|
||||||
const INTERFACE_TYPE = 'command';
|
const INTERFACE_TYPE = 'command';
|
||||||
|
|
||||||
private $workingDirectory;
|
private $workingDirectoryStack = array();
|
||||||
|
|
||||||
public function setWorkingDirectory($working_directory) {
|
public function pushWorkingDirectory($working_directory) {
|
||||||
$this->workingDirectory = $working_directory;
|
$this->workingDirectoryStack[] = $working_directory;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWorkingDirectory() {
|
public function popWorkingDirectory() {
|
||||||
return $this->workingDirectory;
|
if (!$this->workingDirectoryStack) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to pop working directory, directory stack is empty.'));
|
||||||
|
}
|
||||||
|
return array_pop($this->workingDirectoryStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function peekWorkingDirectory() {
|
||||||
|
if ($this->workingDirectoryStack) {
|
||||||
|
return last($this->workingDirectoryStack);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getInterfaceType() {
|
final public function getInterfaceType() {
|
||||||
|
@ -38,12 +50,14 @@ abstract class DrydockCommandInterface extends DrydockInterface {
|
||||||
abstract public function getExecFuture($command);
|
abstract public function getExecFuture($command);
|
||||||
|
|
||||||
protected function applyWorkingDirectoryToArgv(array $argv) {
|
protected function applyWorkingDirectoryToArgv(array $argv) {
|
||||||
if ($this->getWorkingDirectory() !== null) {
|
$directory = $this->peekWorkingDirectory();
|
||||||
|
|
||||||
|
if ($directory !== null) {
|
||||||
$cmd = $argv[0];
|
$cmd = $argv[0];
|
||||||
$cmd = "(cd %s && {$cmd})";
|
$cmd = "(cd %s && {$cmd})";
|
||||||
$argv = array_merge(
|
$argv = array_merge(
|
||||||
array($cmd),
|
array($cmd),
|
||||||
array($this->getWorkingDirectory()),
|
array($directory),
|
||||||
array_slice($argv, 1));
|
array_slice($argv, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,28 @@ final class DrydockLandRepositoryOperation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) {
|
||||||
|
$repository = $operation->getRepository();
|
||||||
|
$merges = array();
|
||||||
|
|
||||||
|
$object = $operation->getObject();
|
||||||
|
if ($object instanceof DifferentialRevision) {
|
||||||
|
$diff = $this->loadDiff($operation);
|
||||||
|
$merges[] = array(
|
||||||
|
'src.uri' => $repository->getStagingURI(),
|
||||||
|
'src.ref' => $diff->getStagingRef(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Invalid or unknown object ("%s") for land operation, expected '.
|
||||||
|
'Differential Revision.',
|
||||||
|
$operation->getObjectPHID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $merges;
|
||||||
|
}
|
||||||
|
|
||||||
public function applyOperation(
|
public function applyOperation(
|
||||||
DrydockRepositoryOperation $operation,
|
DrydockRepositoryOperation $operation,
|
||||||
DrydockInterface $interface) {
|
DrydockInterface $interface) {
|
||||||
|
@ -48,36 +70,7 @@ final class DrydockLandRepositoryOperation
|
||||||
if ($object instanceof DifferentialRevision) {
|
if ($object instanceof DifferentialRevision) {
|
||||||
$revision = $object;
|
$revision = $object;
|
||||||
|
|
||||||
$diff_phid = $operation->getProperty('differential.diffPHID');
|
$diff = $this->loadDiff($operation);
|
||||||
|
|
||||||
$diff = id(new DifferentialDiffQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withPHIDs(array($diff_phid))
|
|
||||||
->executeOne();
|
|
||||||
if (!$diff) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Unable to load diff "%s".',
|
|
||||||
$diff_phid));
|
|
||||||
}
|
|
||||||
|
|
||||||
$diff_revid = $diff->getRevisionID();
|
|
||||||
$revision_id = $revision->getID();
|
|
||||||
if ($diff_revid != $revision_id) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Diff ("%s") has wrong revision ID ("%s", expected "%s").',
|
|
||||||
$diff_phid,
|
|
||||||
$diff_revid,
|
|
||||||
$revision_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
$cmd[] = 'git fetch --no-tags -- %s +%s:%s';
|
|
||||||
$arg[] = $repository->getStagingURI();
|
|
||||||
$arg[] = $diff->getStagingRef();
|
|
||||||
$arg[] = $diff->getStagingRef();
|
|
||||||
|
|
||||||
$merge_src = $diff->getStagingRef();
|
|
||||||
|
|
||||||
$dict = $diff->getDiffAuthorshipDict();
|
$dict = $diff->getDiffAuthorshipDict();
|
||||||
$author_name = idx($dict, 'authorName');
|
$author_name = idx($dict, 'authorName');
|
||||||
|
@ -104,7 +97,6 @@ final class DrydockLandRepositoryOperation
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'branch':
|
case 'branch':
|
||||||
$push_dst = 'refs/heads/'.$name;
|
$push_dst = 'refs/heads/'.$name;
|
||||||
$merge_dst = 'refs/remotes/origin/'.$name;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
|
@ -116,30 +108,24 @@ final class DrydockLandRepositoryOperation
|
||||||
|
|
||||||
$committer_info = $this->getCommitterInfo($operation);
|
$committer_info = $this->getCommitterInfo($operation);
|
||||||
|
|
||||||
$cmd[] = 'git checkout %s';
|
// NOTE: We're doing this commit with "-F -" so we don't run into trouble
|
||||||
$arg[] = $merge_dst;
|
// with enormous commit messages which might otherwise exceed the maximum
|
||||||
|
// size of a command.
|
||||||
|
|
||||||
$cmd[] = 'git merge --no-stat --squash --ff-only -- %s';
|
$future = $interface->getExecFuture(
|
||||||
$arg[] = $merge_src;
|
'git -c user.name=%s -c user.email=%s commit --author %s -F - --',
|
||||||
|
$committer_info['name'],
|
||||||
|
$committer_info['email'],
|
||||||
|
"{$author_name} <{$author_email}>");
|
||||||
|
|
||||||
$cmd[] = 'git -c user.name=%s -c user.email=%s commit --author %s -m %s';
|
$future
|
||||||
|
->write($commit_message)
|
||||||
|
->resolvex();
|
||||||
|
|
||||||
$arg[] = $committer_info['name'];
|
$interface->execx(
|
||||||
$arg[] = $committer_info['email'];
|
'git push origin -- %s:%s',
|
||||||
|
'HEAD',
|
||||||
$arg[] = "{$author_name} <{$author_email}>";
|
$push_dst);
|
||||||
$arg[] = $commit_message;
|
|
||||||
|
|
||||||
$cmd[] = 'git push origin -- %s:%s';
|
|
||||||
$arg[] = 'HEAD';
|
|
||||||
$arg[] = $push_dst;
|
|
||||||
|
|
||||||
$cmd = implode(' && ', $cmd);
|
|
||||||
$argv = array_merge(array($cmd), $arg);
|
|
||||||
|
|
||||||
$result = call_user_func_array(
|
|
||||||
array($interface, 'execx'),
|
|
||||||
$argv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCommitterInfo(DrydockRepositoryOperation $operation) {
|
private function getCommitterInfo(DrydockRepositoryOperation $operation) {
|
||||||
|
@ -172,4 +158,132 @@ final class DrydockLandRepositoryOperation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function loadDiff(DrydockRepositoryOperation $operation) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$revision = $operation->getObject();
|
||||||
|
|
||||||
|
$diff_phid = $operation->getProperty('differential.diffPHID');
|
||||||
|
|
||||||
|
$diff = id(new DifferentialDiffQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($diff_phid))
|
||||||
|
->executeOne();
|
||||||
|
if (!$diff) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to load diff "%s".',
|
||||||
|
$diff_phid));
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff_revid = $diff->getRevisionID();
|
||||||
|
$revision_id = $revision->getID();
|
||||||
|
if ($diff_revid != $revision_id) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Diff ("%s") has wrong revision ID ("%s", expected "%s").',
|
||||||
|
$diff_phid,
|
||||||
|
$diff_revid,
|
||||||
|
$revision_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBarrierToLanding(
|
||||||
|
PhabricatorUser $viewer,
|
||||||
|
DifferentialRevision $revision) {
|
||||||
|
|
||||||
|
$repository = $revision->getRepository();
|
||||||
|
if (!$repository) {
|
||||||
|
return array(
|
||||||
|
'title' => pht('No Repository'),
|
||||||
|
'body' => pht(
|
||||||
|
'This revision is not associated with a known repository. Only '.
|
||||||
|
'revisions associated with a tracked repository can be landed '.
|
||||||
|
'automatically.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$repository->canPerformAutomation()) {
|
||||||
|
return array(
|
||||||
|
'title' => pht('No Repository Automation'),
|
||||||
|
'body' => pht(
|
||||||
|
'The repository this revision is associated with ("%s") is not '.
|
||||||
|
'configured to support automation. Configure automation for the '.
|
||||||
|
'repository to enable revisions to be landed automatically.',
|
||||||
|
$repository->getMonogram()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: At some point we should allow installs to give "land reviewed
|
||||||
|
// code" permission to more users than "push any commit", because it is
|
||||||
|
// a much less powerful operation. For now, just require push so this
|
||||||
|
// doesn't do anything users can't do on their own.
|
||||||
|
$can_push = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$repository,
|
||||||
|
DiffusionPushCapability::CAPABILITY);
|
||||||
|
if (!$can_push) {
|
||||||
|
return array(
|
||||||
|
'title' => pht('Unable to Push'),
|
||||||
|
'body' => pht(
|
||||||
|
'You do not have permission to push to the repository this '.
|
||||||
|
'revision is associated with ("%s"), so you can not land it.',
|
||||||
|
$repository->getMonogram()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
|
||||||
|
if ($revision->getStatus() != $status_accepted) {
|
||||||
|
return array(
|
||||||
|
'title' => pht('Revision Not Accepted'),
|
||||||
|
'body' => pht(
|
||||||
|
'This revision is still under review. Only revisions which have '.
|
||||||
|
'been accepted may land.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for other operations. Eventually this should probably be more
|
||||||
|
// general (e.g., it's OK to land to multiple different branches
|
||||||
|
// simultaneously) but just put this in as a sanity check for now.
|
||||||
|
$other_operations = id(new DrydockRepositoryOperationQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withObjectPHIDs(array($revision->getPHID()))
|
||||||
|
->withOperationTypes(
|
||||||
|
array(
|
||||||
|
$this->getOperationConstant(),
|
||||||
|
))
|
||||||
|
->withOperationStates(
|
||||||
|
array(
|
||||||
|
DrydockRepositoryOperation::STATE_WAIT,
|
||||||
|
DrydockRepositoryOperation::STATE_WORK,
|
||||||
|
DrydockRepositoryOperation::STATE_DONE,
|
||||||
|
))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if ($other_operations) {
|
||||||
|
$any_done = false;
|
||||||
|
foreach ($other_operations as $operation) {
|
||||||
|
if ($operation->isDone()) {
|
||||||
|
$any_done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($any_done) {
|
||||||
|
return array(
|
||||||
|
'title' => pht('Already Complete'),
|
||||||
|
'body' => pht('This revision has already landed.'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return array(
|
||||||
|
'title' => pht('Already In Flight'),
|
||||||
|
'body' => pht('This revision is already landing.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ abstract class DrydockRepositoryOperationType extends Phobject {
|
||||||
DrydockRepositoryOperation $operation,
|
DrydockRepositoryOperation $operation,
|
||||||
PhabricatorUser $viewer);
|
PhabricatorUser $viewer);
|
||||||
|
|
||||||
|
public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
final public function setViewer(PhabricatorUser $viewer) {
|
final public function setViewer(PhabricatorUser $viewer) {
|
||||||
$this->viewer = $viewer;
|
$this->viewer = $viewer;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DrydockTestRepositoryOperation
|
||||||
|
extends DrydockRepositoryOperationType {
|
||||||
|
|
||||||
|
const OPCONST = 'test';
|
||||||
|
|
||||||
|
public function getOperationDescription(
|
||||||
|
DrydockRepositoryOperation $operation,
|
||||||
|
PhabricatorUser $viewer) {
|
||||||
|
return pht('Test Configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOperationCurrentStatus(
|
||||||
|
DrydockRepositoryOperation $operation,
|
||||||
|
PhabricatorUser $viewer) {
|
||||||
|
|
||||||
|
$repository = $operation->getRepository();
|
||||||
|
switch ($operation->getOperationState()) {
|
||||||
|
case DrydockRepositoryOperation::STATE_WAIT:
|
||||||
|
return pht(
|
||||||
|
'Waiting to test configuration for %s...',
|
||||||
|
$repository->getMonogram());
|
||||||
|
case DrydockRepositoryOperation::STATE_WORK:
|
||||||
|
return pht(
|
||||||
|
'Testing configuration for %s. This may take a moment if Drydock '.
|
||||||
|
'has to clone the repository for the first time.',
|
||||||
|
$repository->getMonogram());
|
||||||
|
case DrydockRepositoryOperation::STATE_DONE:
|
||||||
|
return pht(
|
||||||
|
'Success! Automation is configured properly and Drydock can '.
|
||||||
|
'operate on %s.',
|
||||||
|
$repository->getMonogram());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOperation(
|
||||||
|
DrydockRepositoryOperation $operation,
|
||||||
|
DrydockInterface $interface) {
|
||||||
|
$repository = $operation->getRepository();
|
||||||
|
|
||||||
|
if ($repository->isGit()) {
|
||||||
|
$interface->execx('git status');
|
||||||
|
} else if ($repository->isHg()) {
|
||||||
|
$interface->execx('hg status');
|
||||||
|
} else if ($repository->isSVN()) {
|
||||||
|
$interface->execx('svn status');
|
||||||
|
} else {
|
||||||
|
throw new PhutilMethodNotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery {
|
||||||
private $objectPHIDs;
|
private $objectPHIDs;
|
||||||
private $repositoryPHIDs;
|
private $repositoryPHIDs;
|
||||||
private $operationStates;
|
private $operationStates;
|
||||||
|
private $operationTypes;
|
||||||
|
|
||||||
public function withIDs(array $ids) {
|
public function withIDs(array $ids) {
|
||||||
$this->ids = $ids;
|
$this->ids = $ids;
|
||||||
|
@ -33,6 +34,11 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withOperationTypes(array $types) {
|
||||||
|
$this->operationTypes = $types;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function newResultObject() {
|
public function newResultObject() {
|
||||||
return new DrydockRepositoryOperation();
|
return new DrydockRepositoryOperation();
|
||||||
}
|
}
|
||||||
|
@ -139,6 +145,13 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery {
|
||||||
$this->operationStates);
|
$this->operationStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->operationTypes !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'operationType IN (%Ls)',
|
||||||
|
$this->operationTypes);
|
||||||
|
}
|
||||||
|
|
||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ final class DrydockAuthorization extends DrydockDAO
|
||||||
|
|
||||||
public static function getBlueprintStateIcon($state) {
|
public static function getBlueprintStateIcon($state) {
|
||||||
$map = array(
|
$map = array(
|
||||||
self::BLUEPRINTAUTH_REQUESTED => 'fa-exclamation-circle indigo',
|
self::BLUEPRINTAUTH_REQUESTED => 'fa-exclamation-circle pink',
|
||||||
self::BLUEPRINTAUTH_AUTHORIZED => 'fa-check-circle green',
|
self::BLUEPRINTAUTH_AUTHORIZED => 'fa-check-circle green',
|
||||||
self::BLUEPRINTAUTH_DECLINED => 'fa-times red',
|
self::BLUEPRINTAUTH_DECLINED => 'fa-times red',
|
||||||
);
|
);
|
||||||
|
|
|
@ -429,10 +429,7 @@ final class DrydockLease extends DrydockDAO
|
||||||
$this->scheduleUpdate($expires);
|
$this->scheduleUpdate($expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');
|
$this->awakenTasks();
|
||||||
if (is_array($awaken_ids) && $awaken_ids) {
|
|
||||||
PhabricatorWorker::awakenTaskIDs($awaken_ids);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logEvent($type, array $data = array()) {
|
public function logEvent($type, array $data = array()) {
|
||||||
|
@ -454,6 +451,19 @@ final class DrydockLease extends DrydockDAO
|
||||||
return $log->save();
|
return $log->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaken yielded tasks after a state change.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public function awakenTasks() {
|
||||||
|
$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');
|
||||||
|
if (is_array($awaken_ids) && $awaken_ids) {
|
||||||
|
PhabricatorWorker::awakenTaskIDs($awaken_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
|
@ -99,7 +99,7 @@ final class DrydockRepositoryOperation extends DrydockDAO
|
||||||
public static function getOperationStateIcon($state) {
|
public static function getOperationStateIcon($state) {
|
||||||
$map = array(
|
$map = array(
|
||||||
self::STATE_WAIT => 'fa-clock-o',
|
self::STATE_WAIT => 'fa-clock-o',
|
||||||
self::STATE_WORK => 'fa-refresh blue',
|
self::STATE_WORK => 'fa-plane ph-spin blue',
|
||||||
self::STATE_DONE => 'fa-check green',
|
self::STATE_DONE => 'fa-check green',
|
||||||
self::STATE_FAIL => 'fa-times red',
|
self::STATE_FAIL => 'fa-times red',
|
||||||
);
|
);
|
||||||
|
@ -158,6 +158,32 @@ final class DrydockRepositoryOperation extends DrydockDAO
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isDone() {
|
||||||
|
return ($this->getOperationState() === self::STATE_DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWorkingCopyMerges() {
|
||||||
|
return $this->getImplementation()->getWorkingCopyMerges(
|
||||||
|
$this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWorkingCopyLeasePHID($lease_phid) {
|
||||||
|
return $this->setProperty('exec.leasePHID', $lease_phid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWorkingCopyLeasePHID() {
|
||||||
|
return $this->getProperty('exec.leasePHID');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWorkingCopyVCSError(array $error) {
|
||||||
|
return $this->setProperty('exec.workingcopy.error', $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWorkingCopyVCSError() {
|
||||||
|
return $this->getProperty('exec.workingcopy.error');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ final class DrydockObjectAuthorizationView extends AphrontView {
|
||||||
$authorizations = array();
|
$authorizations = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$warnings = array();
|
||||||
$items = array();
|
$items = array();
|
||||||
foreach ($blueprint_phids as $phid) {
|
foreach ($blueprint_phids as $phid) {
|
||||||
$authorization = idx($authorizations, $phid);
|
$authorization = idx($authorizations, $phid);
|
||||||
|
@ -65,10 +66,28 @@ final class DrydockObjectAuthorizationView extends AphrontView {
|
||||||
null,
|
null,
|
||||||
DrydockAuthorization::getBlueprintStateName($state));
|
DrydockAuthorization::getBlueprintStateName($state));
|
||||||
|
|
||||||
|
switch ($state) {
|
||||||
|
case DrydockAuthorization::BLUEPRINTAUTH_REQUESTED:
|
||||||
|
case DrydockAuthorization::BLUEPRINTAUTH_DECLINED:
|
||||||
|
$warnings[] = $authorization;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$items[] = $item;
|
$items[] = $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
$status = new PHUIStatusListView();
|
$status = new PHUIStatusListView();
|
||||||
|
|
||||||
|
if ($warnings) {
|
||||||
|
$status->addItem(
|
||||||
|
id(new PHUIStatusItemView())
|
||||||
|
->setIcon('fa-exclamation-triangle', 'pink')
|
||||||
|
->setTarget(
|
||||||
|
pht(
|
||||||
|
'WARNING: There are %s unapproved authorization(s)!',
|
||||||
|
new PhutilNumber(count($warnings)))));
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$status->addItem($item);
|
$status->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,12 +73,104 @@ final class DrydockRepositoryOperationStatusView
|
||||||
if ($state != DrydockRepositoryOperation::STATE_FAIL) {
|
if ($state != DrydockRepositoryOperation::STATE_FAIL) {
|
||||||
$item->addAttribute($operation->getOperationCurrentStatus($viewer));
|
$item->addAttribute($operation->getOperationCurrentStatus($viewer));
|
||||||
} else {
|
} else {
|
||||||
// TODO: Make this more useful.
|
$vcs_error = $operation->getWorkingCopyVCSError();
|
||||||
|
if ($vcs_error) {
|
||||||
|
switch ($vcs_error['phase']) {
|
||||||
|
case DrydockWorkingCopyBlueprintImplementation::PHASE_SQUASHMERGE:
|
||||||
|
$message = pht(
|
||||||
|
'This change did not merge cleanly. This usually indicates '.
|
||||||
|
'that the change is out of date and needs to be updated.');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$message = pht(
|
||||||
|
'Operation encountered an error while performing repository '.
|
||||||
|
'operations.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item->addAttribute($message);
|
||||||
|
|
||||||
|
$table = $this->renderVCSErrorTable($vcs_error);
|
||||||
|
list($links, $info) = $this->renderDetailToggles($table);
|
||||||
|
|
||||||
|
$item->addAttribute($links);
|
||||||
|
$item->appendChild($info);
|
||||||
|
} else {
|
||||||
$item->addAttribute(pht('Operation encountered an error.'));
|
$item->addAttribute(pht('Operation encountered an error.'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return id(new PHUIObjectItemListView())
|
return id(new PHUIObjectItemListView())
|
||||||
->addItem($item);
|
->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function renderVCSErrorTable(array $vcs_error) {
|
||||||
|
$rows = array();
|
||||||
|
$rows[] = array(pht('Command'), $vcs_error['command']);
|
||||||
|
$rows[] = array(pht('Error'), $vcs_error['err']);
|
||||||
|
$rows[] = array(pht('Stdout'), $vcs_error['stdout']);
|
||||||
|
$rows[] = array(pht('Stderr'), $vcs_error['stderr']);
|
||||||
|
|
||||||
|
$table = id(new AphrontTableView($rows))
|
||||||
|
->setColumnClasses(
|
||||||
|
array(
|
||||||
|
'header',
|
||||||
|
'wide prewrap',
|
||||||
|
));
|
||||||
|
|
||||||
|
return $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderDetailToggles(AphrontTableView $table) {
|
||||||
|
$show_id = celerity_generate_unique_node_id();
|
||||||
|
$hide_id = celerity_generate_unique_node_id();
|
||||||
|
$info_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
|
Javelin::initBehavior('phabricator-reveal-content');
|
||||||
|
|
||||||
|
$show_details = javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'id' => $show_id,
|
||||||
|
'href' => '#',
|
||||||
|
'sigil' => 'reveal-content',
|
||||||
|
'mustcapture' => true,
|
||||||
|
'meta' => array(
|
||||||
|
'hideIDs' => array($show_id),
|
||||||
|
'showIDs' => array($hide_id, $info_id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pht('Show Details'));
|
||||||
|
|
||||||
|
$hide_details = javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'id' => $hide_id,
|
||||||
|
'href' => '#',
|
||||||
|
'sigil' => 'reveal-content',
|
||||||
|
'mustcapture' => true,
|
||||||
|
'style' => 'display: none',
|
||||||
|
'meta' => array(
|
||||||
|
'hideIDs' => array($hide_id, $info_id),
|
||||||
|
'showIDs' => array($show_id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pht('Hide Details'));
|
||||||
|
|
||||||
|
$info = javelin_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'id' => $info_id,
|
||||||
|
'style' => 'display: none',
|
||||||
|
),
|
||||||
|
$table);
|
||||||
|
|
||||||
|
$links = array(
|
||||||
|
$show_details,
|
||||||
|
$hide_details,
|
||||||
|
);
|
||||||
|
|
||||||
|
return array($links, $info);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,17 +309,18 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = id(new DrydockBlueprintQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withBlueprintClasses(array_keys($impls))
|
|
||||||
->withDisabled(false);
|
|
||||||
|
|
||||||
$blueprint_phids = $lease->getAllowedBlueprintPHIDs();
|
$blueprint_phids = $lease->getAllowedBlueprintPHIDs();
|
||||||
if (!$blueprint_phids) {
|
if (!$blueprint_phids) {
|
||||||
$lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);
|
$lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$query = id(new DrydockBlueprintQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs($blueprint_phids)
|
||||||
|
->withBlueprintClasses(array_keys($impls))
|
||||||
|
->withDisabled(false);
|
||||||
|
|
||||||
// The Drydock application itself is allowed to authorize anything. This
|
// The Drydock application itself is allowed to authorize anything. This
|
||||||
// is primarily used for leases generated by CLI administrative tools.
|
// is primarily used for leases generated by CLI administrative tools.
|
||||||
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
|
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
|
||||||
|
@ -751,6 +752,15 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
||||||
->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
|
->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
$lease->logEvent(
|
||||||
|
DrydockLeaseActivationFailureLogType::LOGCONST,
|
||||||
|
array(
|
||||||
|
'class' => get_class($ex),
|
||||||
|
'message' => $ex->getMessage(),
|
||||||
|
));
|
||||||
|
|
||||||
|
$lease->awakenTasks();
|
||||||
|
|
||||||
$this->queueTask(
|
$this->queueTask(
|
||||||
__CLASS__,
|
__CLASS__,
|
||||||
array(
|
array(
|
||||||
|
@ -760,13 +770,6 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
||||||
'objectPHID' => $lease->getPHID(),
|
'objectPHID' => $lease->getPHID(),
|
||||||
));
|
));
|
||||||
|
|
||||||
$lease->logEvent(
|
|
||||||
DrydockLeaseActivationFailureLogType::LOGCONST,
|
|
||||||
array(
|
|
||||||
'class' => get_class($ex),
|
|
||||||
'message' => $ex->getMessage(),
|
|
||||||
));
|
|
||||||
|
|
||||||
throw new PhabricatorWorkerPermanentFailureException(
|
throw new PhabricatorWorkerPermanentFailureException(
|
||||||
pht(
|
pht(
|
||||||
'Permanent failure while activating lease ("%s"): %s',
|
'Permanent failure while activating lease ("%s"): %s',
|
||||||
|
@ -796,6 +799,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
$lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);
|
$lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);
|
||||||
|
|
||||||
|
$lease->awakenTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,9 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
// waiting for a lease we're holding.
|
// waiting for a lease we're holding.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$operation->getImplementation()
|
||||||
|
->setViewer($viewer);
|
||||||
|
|
||||||
$lease = $this->loadWorkingCopyLease($operation);
|
$lease = $this->loadWorkingCopyLease($operation);
|
||||||
|
|
||||||
$interface = $lease->getInterface(
|
$interface = $lease->getInterface(
|
||||||
|
@ -61,9 +64,6 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
// No matter what happens here, destroy the lease away once we're done.
|
// No matter what happens here, destroy the lease away once we're done.
|
||||||
$lease->releaseOnDestruction(true);
|
$lease->releaseOnDestruction(true);
|
||||||
|
|
||||||
$operation->getImplementation()
|
|
||||||
->setViewer($viewer);
|
|
||||||
|
|
||||||
$operation->applyOperation($interface);
|
$operation->applyOperation($interface);
|
||||||
|
|
||||||
} catch (PhabricatorWorkerYieldException $ex) {
|
} catch (PhabricatorWorkerYieldException $ex) {
|
||||||
|
@ -90,6 +90,9 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
// TODO: This is very similar to leasing in Harbormaster, maybe we can
|
// TODO: This is very similar to leasing in Harbormaster, maybe we can
|
||||||
// share some of the logic?
|
// share some of the logic?
|
||||||
|
|
||||||
|
$working_copy = new DrydockWorkingCopyBlueprintImplementation();
|
||||||
|
$working_copy_type = $working_copy->getType();
|
||||||
|
|
||||||
$lease_phid = $operation->getProperty('exec.leasePHID');
|
$lease_phid = $operation->getProperty('exec.leasePHID');
|
||||||
if ($lease_phid) {
|
if ($lease_phid) {
|
||||||
$lease = id(new DrydockLeaseQuery())
|
$lease = id(new DrydockLeaseQuery())
|
||||||
|
@ -103,9 +106,6 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
$lease_phid));
|
$lease_phid));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
|
|
||||||
->getType();
|
|
||||||
|
|
||||||
$repository = $operation->getRepository();
|
$repository = $operation->getRepository();
|
||||||
|
|
||||||
$allowed_phids = $repository->getAutomationBlueprintPHIDs();
|
$allowed_phids = $repository->getAutomationBlueprintPHIDs();
|
||||||
|
@ -127,7 +127,7 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
}
|
}
|
||||||
|
|
||||||
$operation
|
$operation
|
||||||
->setProperty('exec.leasePHID', $lease->getPHID())
|
->setWorkingCopyLeasePHID($lease->getPHID())
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
$lease->queueForActivation();
|
$lease->queueForActivation();
|
||||||
|
@ -138,6 +138,13 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$lease->isActive()) {
|
if (!$lease->isActive()) {
|
||||||
|
$vcs_error = $working_copy->getWorkingCopyVCSError($lease);
|
||||||
|
if ($vcs_error) {
|
||||||
|
$operation
|
||||||
|
->setWorkingCopyVCSError($vcs_error)
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
|
||||||
throw new PhabricatorWorkerPermanentFailureException(
|
throw new PhabricatorWorkerPermanentFailureException(
|
||||||
pht(
|
pht(
|
||||||
'Lease "%s" never activated.',
|
'Lease "%s" never activated.',
|
||||||
|
@ -158,6 +165,9 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
'branch' => $name,
|
'branch' => $name,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'none':
|
||||||
|
$spec = array();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
|
@ -166,6 +176,8 @@ final class DrydockRepositoryOperationUpdateWorker
|
||||||
$target));
|
$target));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$spec['merges'] = $operation->getWorkingCopyMerges();
|
||||||
|
|
||||||
$map = array();
|
$map = array();
|
||||||
$map[$repository->getCloneName()] = array(
|
$map[$repository->getCloneName()] = array(
|
||||||
'phid' => $repository->getPHID(),
|
'phid' => $repository->getPHID(),
|
||||||
|
|
|
@ -61,6 +61,7 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
|
||||||
'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController',
|
'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController',
|
||||||
'new/(?P<plan>\d+)/(?P<class>[^/]+)/'
|
'new/(?P<plan>\d+)/(?P<class>[^/]+)/'
|
||||||
=> 'HarbormasterStepEditController',
|
=> 'HarbormasterStepEditController',
|
||||||
|
'view/(?P<id>\d+)/' => 'HarbormasterStepViewController',
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController',
|
||||||
'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController',
|
'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController',
|
||||||
),
|
),
|
||||||
|
@ -95,8 +96,16 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
|
||||||
|
|
||||||
protected function getCustomCapabilities() {
|
protected function getCustomCapabilities() {
|
||||||
return array(
|
return array(
|
||||||
HarbormasterManagePlansCapability::CAPABILITY => array(
|
HarbormasterCreatePlansCapability::CAPABILITY => array(
|
||||||
'caption' => pht('Can create and manage build plans.'),
|
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||||
|
),
|
||||||
|
HarbormasterBuildPlanDefaultViewCapability::CAPABILITY => array(
|
||||||
|
'template' => HarbormasterBuildPlanPHIDType::TYPECONST,
|
||||||
|
'capability' => PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
),
|
||||||
|
HarbormasterBuildPlanDefaultEditCapability::CAPABILITY => array(
|
||||||
|
'template' => HarbormasterBuildPlanPHIDType::TYPECONST,
|
||||||
|
'capability' => PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterBuildPlanDefaultEditCapability
|
||||||
|
extends PhabricatorPolicyCapability {
|
||||||
|
|
||||||
|
const CAPABILITY = 'harbormaster.plan.default.edit';
|
||||||
|
|
||||||
|
public function getCapabilityName() {
|
||||||
|
return pht('Default Build Plan Edit Policy');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterBuildPlanDefaultViewCapability
|
||||||
|
extends PhabricatorPolicyCapability {
|
||||||
|
|
||||||
|
const CAPABILITY = 'harbomaster.plan.default.view';
|
||||||
|
|
||||||
|
public function getCapabilityName() {
|
||||||
|
return pht('Default Build Plan View Policy');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldAllowPublicPolicySetting() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class HarbormasterManagePlansCapability
|
final class HarbormasterCreatePlansCapability
|
||||||
extends PhabricatorPolicyCapability {
|
extends PhabricatorPolicyCapability {
|
||||||
|
|
||||||
const CAPABILITY = 'harbormaster.plans';
|
const CAPABILITY = 'harbormaster.plans';
|
||||||
|
|
||||||
public function getCapabilityName() {
|
public function getCapabilityName() {
|
||||||
return pht('Can Manage Build Plans');
|
return pht('Can Create Build Plans');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function describeCapabilityRejection() {
|
public function describeCapabilityRejection() {
|
||||||
return pht(
|
return pht(
|
||||||
'You do not have permission to manage Harbormaster build plans.');
|
'You do not have permission to create Harbormaster build plans.');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,9 +6,6 @@ final class HarbormasterPlanDisableController
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$plan = id(new HarbormasterBuildPlanQuery())
|
$plan = id(new HarbormasterBuildPlanQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIDs(array($request->getURIData('id')))
|
->withIDs(array($request->getURIData('id')))
|
||||||
|
|
|
@ -5,9 +5,6 @@ final class HarbormasterPlanEditController extends HarbormasterPlanController {
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$id = $request->getURIData('id');
|
$id = $request->getURIData('id');
|
||||||
if ($id) {
|
if ($id) {
|
||||||
$plan = id(new HarbormasterBuildPlanQuery())
|
$plan = id(new HarbormasterBuildPlanQuery())
|
||||||
|
@ -23,23 +20,42 @@ final class HarbormasterPlanEditController extends HarbormasterPlanController {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
$this->requireApplicationCapability(
|
||||||
|
HarbormasterCreatePlansCapability::CAPABILITY);
|
||||||
|
|
||||||
$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
|
$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
$e_name = true;
|
$e_name = true;
|
||||||
$v_name = $plan->getName();
|
$v_name = $plan->getName();
|
||||||
|
$v_view = $plan->getViewPolicy();
|
||||||
|
$v_edit = $plan->getEditPolicy();
|
||||||
$validation_exception = null;
|
$validation_exception = null;
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
$v_name = $request->getStr('name');
|
$v_name = $request->getStr('name');
|
||||||
|
$v_view = $request->getStr('viewPolicy');
|
||||||
|
$v_edit = $request->getStr('editPolicy');
|
||||||
|
|
||||||
$e_name = null;
|
$e_name = null;
|
||||||
|
|
||||||
$type_name = HarbormasterBuildPlanTransaction::TYPE_NAME;
|
$type_name = HarbormasterBuildPlanTransaction::TYPE_NAME;
|
||||||
|
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||||
|
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||||
|
|
||||||
$xactions[] = id(new HarbormasterBuildPlanTransaction())
|
$xactions[] = id(new HarbormasterBuildPlanTransaction())
|
||||||
->setTransactionType($type_name)
|
->setTransactionType($type_name)
|
||||||
->setNewValue($v_name);
|
->setNewValue($v_name);
|
||||||
|
|
||||||
|
$xactions[] = id(new HarbormasterBuildPlanTransaction())
|
||||||
|
->setTransactionType($type_view)
|
||||||
|
->setNewValue($v_view);
|
||||||
|
|
||||||
|
$xactions[] = id(new HarbormasterBuildPlanTransaction())
|
||||||
|
->setTransactionType($type_edit)
|
||||||
|
->setNewValue($v_edit);
|
||||||
|
|
||||||
$editor = id(new HarbormasterBuildPlanEditor())
|
$editor = id(new HarbormasterBuildPlanEditor())
|
||||||
->setActor($viewer)
|
->setActor($viewer)
|
||||||
->setContinueOnNoEffect(true)
|
->setContinueOnNoEffect(true)
|
||||||
|
@ -71,16 +87,34 @@ final class HarbormasterPlanEditController extends HarbormasterPlanController {
|
||||||
$save_button = pht('Save Build Plan');
|
$save_button = pht('Save Build Plan');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$policies = id(new PhabricatorPolicyQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setObject($plan)
|
||||||
|
->execute();
|
||||||
|
|
||||||
$form = id(new AphrontFormView())
|
$form = id(new AphrontFormView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->appendChild(
|
->appendControl(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel(pht('Plan Name'))
|
->setLabel(pht('Plan Name'))
|
||||||
->setName('name')
|
->setName('name')
|
||||||
->setError($e_name)
|
->setError($e_name)
|
||||||
->setValue($v_name));
|
->setValue($v_name))
|
||||||
|
->appendControl(
|
||||||
$form->appendChild(
|
id(new AphrontFormPolicyControl())
|
||||||
|
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
|
||||||
|
->setPolicyObject($plan)
|
||||||
|
->setPolicies($policies)
|
||||||
|
->setValue($v_view)
|
||||||
|
->setName('viewPolicy'))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormPolicyControl())
|
||||||
|
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
|
||||||
|
->setPolicyObject($plan)
|
||||||
|
->setPolicies($policies)
|
||||||
|
->setValue($v_edit)
|
||||||
|
->setName('editPolicy'))
|
||||||
|
->appendControl(
|
||||||
id(new AphrontFormSubmitControl())
|
id(new AphrontFormSubmitControl())
|
||||||
->setValue($save_button)
|
->setValue($save_button)
|
||||||
->addCancelButton($cancel_uri));
|
->addCancelButton($cancel_uri));
|
||||||
|
|
|
@ -42,7 +42,7 @@ final class HarbormasterPlanListController extends HarbormasterPlanController {
|
||||||
$crumbs = parent::buildApplicationCrumbs();
|
$crumbs = parent::buildApplicationCrumbs();
|
||||||
|
|
||||||
$can_create = $this->hasApplicationCapability(
|
$can_create = $this->hasApplicationCapability(
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
HarbormasterCreatePlansCapability::CAPABILITY);
|
||||||
|
|
||||||
$crumbs->addAction(
|
$crumbs->addAction(
|
||||||
id(new PHUIListItemView())
|
id(new PHUIListItemView())
|
||||||
|
|
|
@ -4,19 +4,16 @@ final class HarbormasterPlanRunController extends HarbormasterController {
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$plan_id = $request->getURIData('id');
|
$plan_id = $request->getURIData('id');
|
||||||
|
|
||||||
// NOTE: At least for now, this only requires the "Can Manage Plans"
|
|
||||||
// capability, not the "Can Edit" capability. Possibly it should have
|
|
||||||
// a more stringent requirement, though.
|
|
||||||
|
|
||||||
$plan = id(new HarbormasterBuildPlanQuery())
|
$plan = id(new HarbormasterBuildPlanQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIDs(array($plan_id))
|
->withIDs(array($plan_id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getviewer();
|
$viewer = $this->getViewer();
|
||||||
$id = $request->getURIData('id');
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
$plan = id(new HarbormasterBuildPlanQuery())
|
$plan = id(new HarbormasterBuildPlanQuery())
|
||||||
|
@ -81,16 +81,11 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
||||||
->execute();
|
->execute();
|
||||||
$steps = mpull($steps, null, 'getPHID');
|
$steps = mpull($steps, null, 'getPHID');
|
||||||
|
|
||||||
$has_manage = $this->hasApplicationCapability(
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$has_edit = PhabricatorPolicyFilter::hasCapability(
|
|
||||||
$viewer,
|
$viewer,
|
||||||
$plan,
|
$plan,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
$can_edit = ($has_manage && $has_edit);
|
|
||||||
|
|
||||||
$step_list = id(new PHUIObjectItemListView())
|
$step_list = id(new PHUIObjectItemListView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->setNoDataString(
|
->setNoDataString(
|
||||||
|
@ -122,16 +117,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
||||||
->setStatusIcon('fa-warning red')
|
->setStatusIcon('fa-warning red')
|
||||||
->addAttribute(pht(
|
->addAttribute(pht(
|
||||||
'This step has an invalid implementation (%s).',
|
'This step has an invalid implementation (%s).',
|
||||||
$step->getClassName()))
|
$step->getClassName()));
|
||||||
->addAction(
|
|
||||||
id(new PHUIListItemView())
|
|
||||||
->setIcon('fa-times')
|
|
||||||
->addSigil('harbormaster-build-step-delete')
|
|
||||||
->setWorkflow(true)
|
|
||||||
->setRenderNameAsTooltip(true)
|
|
||||||
->setName(pht('Delete'))
|
|
||||||
->setHref(
|
|
||||||
$this->getApplicationURI('step/delete/'.$step->getID().'/')));
|
|
||||||
$step_list->addItem($item);
|
$step_list->addItem($item);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -142,23 +128,9 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
||||||
$item->addAttribute($implementation->getDescription());
|
$item->addAttribute($implementation->getDescription());
|
||||||
|
|
||||||
$step_id = $step->getID();
|
$step_id = $step->getID();
|
||||||
$edit_uri = $this->getApplicationURI("step/edit/{$step_id}/");
|
|
||||||
$delete_uri = $this->getApplicationURI("step/delete/{$step_id}/");
|
|
||||||
|
|
||||||
if ($can_edit) {
|
$view_uri = $this->getApplicationURI("step/view/{$step_id}/");
|
||||||
$item->setHref($edit_uri);
|
$item->setHref($view_uri);
|
||||||
}
|
|
||||||
|
|
||||||
$item
|
|
||||||
->setHref($edit_uri)
|
|
||||||
->addAction(
|
|
||||||
id(new PHUIListItemView())
|
|
||||||
->setIcon('fa-times')
|
|
||||||
->addSigil('harbormaster-build-step-delete')
|
|
||||||
->setWorkflow(true)
|
|
||||||
->setDisabled(!$can_edit)
|
|
||||||
->setHref(
|
|
||||||
$this->getApplicationURI('step/delete/'.$step->getID().'/')));
|
|
||||||
|
|
||||||
$depends = $step->getStepImplementation()->getDependencies($step);
|
$depends = $step->getStepImplementation()->getDependencies($step);
|
||||||
$inputs = $step->getStepImplementation()->getArtifactInputs();
|
$inputs = $step->getStepImplementation()->getArtifactInputs();
|
||||||
|
@ -252,16 +224,11 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
||||||
->setObject($plan)
|
->setObject($plan)
|
||||||
->setObjectURI($this->getApplicationURI("plan/{$id}/"));
|
->setObjectURI($this->getApplicationURI("plan/{$id}/"));
|
||||||
|
|
||||||
$has_manage = $this->hasApplicationCapability(
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$has_edit = PhabricatorPolicyFilter::hasCapability(
|
|
||||||
$viewer,
|
$viewer,
|
||||||
$plan,
|
$plan,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
$can_edit = ($has_manage && $has_edit);
|
|
||||||
|
|
||||||
$list->addAction(
|
$list->addAction(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
->setName(pht('Edit Plan'))
|
->setName(pht('Edit Plan'))
|
||||||
|
@ -288,7 +255,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
||||||
->setIcon('fa-ban'));
|
->setIcon('fa-ban'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$can_run = ($has_manage && $plan->canRunManually());
|
$can_run = ($can_edit && $plan->canRunManually());
|
||||||
|
|
||||||
$list->addAction(
|
$list->addAction(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
|
|
|
@ -5,9 +5,6 @@ final class HarbormasterStepAddController extends HarbormasterController {
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$plan = id(new HarbormasterBuildPlanQuery())
|
$plan = id(new HarbormasterBuildPlanQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIDs(array($request->getURIData('id')))
|
->withIDs(array($request->getURIData('id')))
|
||||||
|
|
|
@ -5,9 +5,6 @@ final class HarbormasterStepDeleteController extends HarbormasterController {
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$id = $request->getURIData('id');
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
$step = id(new HarbormasterBuildStepQuery())
|
$step = id(new HarbormasterBuildStepQuery())
|
||||||
|
|
|
@ -6,9 +6,6 @@ final class HarbormasterStepEditController extends HarbormasterController {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
$id = $request->getURIData('id');
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
|
||||||
HarbormasterManagePlansCapability::CAPABILITY);
|
|
||||||
|
|
||||||
if ($id) {
|
if ($id) {
|
||||||
$step = id(new HarbormasterBuildStepQuery())
|
$step = id(new HarbormasterBuildStepQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
|
@ -61,6 +58,12 @@ final class HarbormasterStepEditController extends HarbormasterController {
|
||||||
|
|
||||||
$plan_uri = $this->getApplicationURI('plan/'.$plan->getID().'/');
|
$plan_uri = $this->getApplicationURI('plan/'.$plan->getID().'/');
|
||||||
|
|
||||||
|
if ($is_new) {
|
||||||
|
$cancel_uri = $plan_uri;
|
||||||
|
} else {
|
||||||
|
$cancel_uri = $this->getApplicationURI('step/view/'.$step->getID().'/');
|
||||||
|
}
|
||||||
|
|
||||||
$implementation = $step->getStepImplementation();
|
$implementation = $step->getStepImplementation();
|
||||||
|
|
||||||
$field_list = PhabricatorCustomField::getObjectFields(
|
$field_list = PhabricatorCustomField::getObjectFields(
|
||||||
|
@ -122,7 +125,10 @@ final class HarbormasterStepEditController extends HarbormasterController {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$editor->applyTransactions($step, $xactions);
|
$editor->applyTransactions($step, $xactions);
|
||||||
return id(new AphrontRedirectResponse())->setURI($plan_uri);
|
|
||||||
|
$step_uri = $this->getApplicationURI('step/view/'.$step->getID().'/');
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($step_uri);
|
||||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||||
$validation_exception = $ex;
|
$validation_exception = $ex;
|
||||||
}
|
}
|
||||||
|
@ -165,31 +171,31 @@ final class HarbormasterStepEditController extends HarbormasterController {
|
||||||
->setError($e_description)
|
->setError($e_description)
|
||||||
->setValue($v_description));
|
->setValue($v_description));
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$id = $plan->getID();
|
||||||
|
$crumbs->addTextCrumb(pht('Plan %d', $id), $plan_uri);
|
||||||
|
|
||||||
if ($is_new) {
|
if ($is_new) {
|
||||||
$submit = pht('Create Build Step');
|
$submit = pht('Create Build Step');
|
||||||
$header = pht('New Step: %s', $implementation->getName());
|
$header = pht('New Step: %s', $implementation->getName());
|
||||||
$crumb = pht('Add Step');
|
$crumbs->addTextCrumb(pht('Add Step'));
|
||||||
} else {
|
} else {
|
||||||
$submit = pht('Save Build Step');
|
$submit = pht('Save Build Step');
|
||||||
$header = pht('Edit Step: %s', $implementation->getName());
|
$header = pht('Edit Step: %s', $implementation->getName());
|
||||||
$crumb = pht('Edit Step');
|
$crumbs->addTextCrumb(pht('Step %d', $step->getID()), $cancel_uri);
|
||||||
|
$crumbs->addTextCrumb(pht('Edit Step'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$form->appendChild(
|
$form->appendChild(
|
||||||
id(new AphrontFormSubmitControl())
|
id(new AphrontFormSubmitControl())
|
||||||
->setValue($submit)
|
->setValue($submit)
|
||||||
->addCancelButton($plan_uri));
|
->addCancelButton($cancel_uri));
|
||||||
|
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
->setHeaderText($header)
|
->setHeaderText($header)
|
||||||
->setValidationException($validation_exception)
|
->setValidationException($validation_exception)
|
||||||
->setForm($form);
|
->setForm($form);
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
|
||||||
$id = $plan->getID();
|
|
||||||
$crumbs->addTextCrumb(pht('Plan %d', $id), $plan_uri);
|
|
||||||
$crumbs->addTextCrumb($crumb);
|
|
||||||
|
|
||||||
$variables = $this->renderBuildVariablesTable();
|
$variables = $this->renderBuildVariablesTable();
|
||||||
|
|
||||||
if ($is_new) {
|
if ($is_new) {
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterStepViewController extends HarbormasterController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
|
$step = id(new HarbormasterBuildStepQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->executeOne();
|
||||||
|
if (!$step) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
$plan = $step->getBuildPlan();
|
||||||
|
|
||||||
|
$plan_id = $plan->getID();
|
||||||
|
$plan_uri = $this->getApplicationURI("plan/{$plan_id}/");
|
||||||
|
|
||||||
|
$implementation = $step->getStepImplementation();
|
||||||
|
|
||||||
|
$field_list = PhabricatorCustomField::getObjectFields(
|
||||||
|
$step,
|
||||||
|
PhabricatorCustomField::ROLE_VIEW);
|
||||||
|
$field_list
|
||||||
|
->setViewer($viewer)
|
||||||
|
->readFieldsFromStorage($step);
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addTextCrumb(pht('Plan %d', $plan_id), $plan_uri);
|
||||||
|
$crumbs->addTextCrumb(pht('Step %d', $id));
|
||||||
|
|
||||||
|
$box = id(new PHUIObjectBoxView())
|
||||||
|
->setHeaderText(pht('Build Step %d: %s', $id, $step->getName()));
|
||||||
|
|
||||||
|
$properties = $this->buildPropertyList($step, $field_list);
|
||||||
|
$actions = $this->buildActionList($step);
|
||||||
|
$properties->setActionList($actions);
|
||||||
|
|
||||||
|
$box->addPropertyList($properties);
|
||||||
|
|
||||||
|
$timeline = $this->buildTransactionTimeline(
|
||||||
|
$step,
|
||||||
|
new HarbormasterBuildStepTransactionQuery());
|
||||||
|
$timeline->setShouldTerminate(true);
|
||||||
|
|
||||||
|
return $this->buildApplicationPage(
|
||||||
|
array(
|
||||||
|
$crumbs,
|
||||||
|
$box,
|
||||||
|
$timeline,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => pht('Step %d', $id),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPropertyList(
|
||||||
|
HarbormasterBuildStep $step,
|
||||||
|
PhabricatorCustomFieldList $field_list) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setObject($step);
|
||||||
|
|
||||||
|
$view->addProperty(
|
||||||
|
pht('Created'),
|
||||||
|
phabricator_datetime($step->getDateCreated(), $viewer));
|
||||||
|
|
||||||
|
$field_list->appendFieldsToPropertyList(
|
||||||
|
$step,
|
||||||
|
$viewer,
|
||||||
|
$view);
|
||||||
|
|
||||||
|
$view->invokeWillRenderEvent();
|
||||||
|
|
||||||
|
$description = $step->getDescription();
|
||||||
|
if (strlen($description)) {
|
||||||
|
$view->addSectionHeader(
|
||||||
|
pht('Description'),
|
||||||
|
PHUIPropertyListView::ICON_SUMMARY);
|
||||||
|
$view->addTextContent(
|
||||||
|
new PHUIRemarkupView($viewer, $description));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $view;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function buildActionList(HarbormasterBuildStep $step) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$id = $step->getID();
|
||||||
|
|
||||||
|
$list = id(new PhabricatorActionListView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setObject($step);
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$step,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$list->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setName(pht('Edit Step'))
|
||||||
|
->setHref($this->getApplicationURI("step/edit/{$id}/"))
|
||||||
|
->setWorkflow(!$can_edit)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setIcon('fa-pencil'));
|
||||||
|
|
||||||
|
$list->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setName(pht('Delete Step'))
|
||||||
|
->setHref($this->getApplicationURI("step/delete/{$id}/"))
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setIcon('fa-times'));
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ final class HarbormasterBuildPlanEditor
|
||||||
$types = parent::getTransactionTypes();
|
$types = parent::getTransactionTypes();
|
||||||
$types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
|
$types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
|
||||||
$types[] = HarbormasterBuildPlanTransaction::TYPE_STATUS;
|
$types[] = HarbormasterBuildPlanTransaction::TYPE_STATUS;
|
||||||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||||
|
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||||
return $types;
|
return $types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,12 @@ final class HarbormasterManagementBuildWorkflow
|
||||||
'param' => 'id',
|
'param' => 'id',
|
||||||
'help' => pht('ID of build plan to run.'),
|
'help' => pht('ID of build plan to run.'),
|
||||||
),
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'background',
|
||||||
|
'help' => pht(
|
||||||
|
'Submit builds into the build queue normally instead of '.
|
||||||
|
'running them in the foreground.'),
|
||||||
|
),
|
||||||
array(
|
array(
|
||||||
'name' => 'buildable',
|
'name' => 'buildable',
|
||||||
'wildcard' => true,
|
'wildcard' => true,
|
||||||
|
@ -88,7 +94,10 @@ final class HarbormasterManagementBuildWorkflow
|
||||||
"\n %s\n\n",
|
"\n %s\n\n",
|
||||||
PhabricatorEnv::getProductionURI('/B'.$buildable->getID()));
|
PhabricatorEnv::getProductionURI('/B'.$buildable->getID()));
|
||||||
|
|
||||||
|
if (!$args->getArg('background')) {
|
||||||
PhabricatorWorker::setRunAllTasksInProcess(true);
|
PhabricatorWorker::setRunAllTasksInProcess(true);
|
||||||
|
}
|
||||||
|
|
||||||
$buildable->applyPlan($plan, array());
|
$buildable->applyPlan($plan, array());
|
||||||
|
|
||||||
$console->writeOut("%s\n", pht('Done.'));
|
$console->writeOut("%s\n", pht('Done.'));
|
||||||
|
|
|
@ -34,7 +34,7 @@ final class HarbormasterBuildStepPHIDType extends PhabricatorPHIDType {
|
||||||
$handle
|
$handle
|
||||||
->setName($name)
|
->setName($name)
|
||||||
->setFullName(pht('Build Step %d: %s', $id, $name))
|
->setFullName(pht('Build Step %d: %s', $id, $name))
|
||||||
->setURI("/harbormaster/step/{$id}/edit/");
|
->setURI("/harbormaster/step/view/{$id}/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,10 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
|
||||||
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
|
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
|
||||||
->getType();
|
->getType();
|
||||||
|
|
||||||
$allowed_phids = $build_target->getFieldValue('repositoryPHIDs');
|
$allowed_phids = $build_target->getFieldValue('blueprintPHIDs');
|
||||||
|
if (!is_array($allowed_phids)) {
|
||||||
|
$allowed_phids = array();
|
||||||
|
}
|
||||||
$authorizing_phid = $build_target->getBuildStep()->getPHID();
|
$authorizing_phid = $build_target->getBuildStep()->getPHID();
|
||||||
|
|
||||||
$lease = DrydockLease::initializeNewLease()
|
$lease = DrydockLease::initializeNewLease()
|
||||||
|
@ -135,6 +138,9 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
|
||||||
}
|
}
|
||||||
|
|
||||||
$also_phids = $build_target->getFieldValue('repositoryPHIDs');
|
$also_phids = $build_target->getFieldValue('repositoryPHIDs');
|
||||||
|
if (!is_array($also_phids)) {
|
||||||
|
$also_phids = array();
|
||||||
|
}
|
||||||
|
|
||||||
$all_phids = $also_phids;
|
$all_phids = $also_phids;
|
||||||
$all_phids[] = $repository_phid;
|
$all_phids[] = $repository_phid;
|
||||||
|
|
|
@ -12,6 +12,8 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $planStatus;
|
protected $planStatus;
|
||||||
protected $planAutoKey;
|
protected $planAutoKey;
|
||||||
|
protected $viewPolicy;
|
||||||
|
protected $editPolicy;
|
||||||
|
|
||||||
const STATUS_ACTIVE = 'active';
|
const STATUS_ACTIVE = 'active';
|
||||||
const STATUS_DISABLED = 'disabled';
|
const STATUS_DISABLED = 'disabled';
|
||||||
|
@ -19,10 +21,22 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
||||||
private $buildSteps = self::ATTACHABLE;
|
private $buildSteps = self::ATTACHABLE;
|
||||||
|
|
||||||
public static function initializeNewBuildPlan(PhabricatorUser $actor) {
|
public static function initializeNewBuildPlan(PhabricatorUser $actor) {
|
||||||
|
$app = id(new PhabricatorApplicationQuery())
|
||||||
|
->setViewer($actor)
|
||||||
|
->withClasses(array('PhabricatorHarbormasterApplication'))
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
$view_policy = $app->getPolicy(
|
||||||
|
HarbormasterBuildPlanDefaultViewCapability::CAPABILITY);
|
||||||
|
$edit_policy = $app->getPolicy(
|
||||||
|
HarbormasterBuildPlanDefaultEditCapability::CAPABILITY);
|
||||||
|
|
||||||
return id(new HarbormasterBuildPlan())
|
return id(new HarbormasterBuildPlan())
|
||||||
->setName('')
|
->setName('')
|
||||||
->setPlanStatus(self::STATUS_ACTIVE)
|
->setPlanStatus(self::STATUS_ACTIVE)
|
||||||
->attachBuildSteps(array());
|
->attachBuildSteps(array())
|
||||||
|
->setViewPolicy($view_policy)
|
||||||
|
->setEditPolicy($edit_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getConfiguration() {
|
protected function getConfiguration() {
|
||||||
|
@ -156,16 +170,15 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
|
||||||
public function getPolicy($capability) {
|
public function getPolicy($capability) {
|
||||||
switch ($capability) {
|
switch ($capability) {
|
||||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||||
|
if ($this->isAutoplan()) {
|
||||||
return PhabricatorPolicies::getMostOpenPolicy();
|
return PhabricatorPolicies::getMostOpenPolicy();
|
||||||
|
}
|
||||||
|
return $this->getViewPolicy();
|
||||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||||
// NOTE: In practice, this policy is always limited by the "Mangage
|
|
||||||
// Build Plans" policy.
|
|
||||||
|
|
||||||
if ($this->isAutoplan()) {
|
if ($this->isAutoplan()) {
|
||||||
return PhabricatorPolicies::POLICY_NOONE;
|
return PhabricatorPolicies::POLICY_NOONE;
|
||||||
}
|
}
|
||||||
|
return $this->getEditPolicy();
|
||||||
return PhabricatorPolicies::getMostOpenPolicy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ final class PhabricatorIconRemarkupRule extends PhutilRemarkupRule {
|
||||||
|
|
||||||
$defaults = array(
|
$defaults = array(
|
||||||
'color' => null,
|
'color' => null,
|
||||||
|
'spin' => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
$options = idx($extra, 1, '');
|
$options = idx($extra, 1, '');
|
||||||
|
@ -70,8 +71,16 @@ final class PhabricatorIconRemarkupRule extends PhutilRemarkupRule {
|
||||||
$color = null;
|
$color = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$classes = array();
|
||||||
|
$classes[] = $color;
|
||||||
|
|
||||||
|
$spin = $options['spin'];
|
||||||
|
if ($spin) {
|
||||||
|
$classes[] = 'ph-spin';
|
||||||
|
}
|
||||||
|
|
||||||
$icon_view = id(new PHUIIconView())
|
$icon_view = id(new PHUIIconView())
|
||||||
->setIconFont('fa-'.$icon, $color);
|
->setIconFont('fa-'.$icon, implode(' ', $classes));
|
||||||
|
|
||||||
return $this->getEngine()->storeText($icon_view);
|
return $this->getEngine()->storeText($icon_view);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ final class PhabricatorMacroMacroPHIDType extends PhabricatorPHIDType {
|
||||||
return pht('Image Macro');
|
return pht('Image Macro');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPHIDTypeApplicationClass() {
|
||||||
|
return 'PhabricatorMacroApplication';
|
||||||
|
}
|
||||||
|
|
||||||
public function getTypeIcon() {
|
public function getTypeIcon() {
|
||||||
return 'fa-meh-o';
|
return 'fa-meh-o';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: This class is just here to keep `storage adjust` happy until we
|
|
||||||
* destroy the table.
|
|
||||||
*/
|
|
||||||
final class PhabricatorMetaMTAMailingList extends PhabricatorMetaMTADAO {
|
|
||||||
|
|
||||||
protected $name;
|
|
||||||
protected $email;
|
|
||||||
protected $uri;
|
|
||||||
|
|
||||||
|
|
||||||
protected function getConfiguration() {
|
|
||||||
return array(
|
|
||||||
self::CONFIG_AUX_PHID => true,
|
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
|
||||||
'name' => 'text128',
|
|
||||||
'email' => 'text128',
|
|
||||||
'uri' => 'text255?',
|
|
||||||
),
|
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
|
||||||
'key_phid' => null,
|
|
||||||
'phid' => array(
|
|
||||||
'columns' => array('phid'),
|
|
||||||
'unique' => true,
|
|
||||||
),
|
|
||||||
'email' => array(
|
|
||||||
'columns' => array('email'),
|
|
||||||
'unique' => true,
|
|
||||||
),
|
|
||||||
'name' => array(
|
|
||||||
'columns' => array('name'),
|
|
||||||
'unique' => true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
) + parent::getConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -72,11 +72,14 @@ final class PhabricatorPeopleProfileController
|
||||||
$href = id(new PhutilURI('/conpherence/new/'))
|
$href = id(new PhutilURI('/conpherence/new/'))
|
||||||
->setQueryParam('participant', $user->getPHID());
|
->setQueryParam('participant', $user->getPHID());
|
||||||
|
|
||||||
|
$can_send = $viewer->isLoggedIn();
|
||||||
|
|
||||||
$actions->addAction(
|
$actions->addAction(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
->setIcon('fa-comments')
|
->setIcon('fa-comments')
|
||||||
->setName(pht('Send Message'))
|
->setName(pht('Send Message'))
|
||||||
->setWorkflow(true)
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_send)
|
||||||
->setHref($href));
|
->setHref($href));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ final class PonderAnswerPHIDType extends PhabricatorPHIDType {
|
||||||
return pht('Ponder Answer');
|
return pht('Ponder Answer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPHIDTypeApplicationClass() {
|
||||||
|
return 'PhabricatorPonderApplication';
|
||||||
|
}
|
||||||
|
|
||||||
public function newObject() {
|
public function newObject() {
|
||||||
return new PonderAnswer();
|
return new PonderAnswer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,15 +54,14 @@ final class SubscriptionListDialogBuilder extends Phobject {
|
||||||
->setUser($this->getViewer())
|
->setUser($this->getViewer())
|
||||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
->setTitle($this->getTitle())
|
->setTitle($this->getTitle())
|
||||||
->appendChild($this->buildBody($this->getViewer(), $handles))
|
->setObjectList($this->buildBody($this->getViewer(), $handles))
|
||||||
->addCancelButton($object_handle->getURI(), pht('Close'));
|
->addCancelButton($object_handle->getURI(), pht('Close'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildBody(PhabricatorUser $viewer, array $handles) {
|
private function buildBody(PhabricatorUser $viewer, array $handles) {
|
||||||
|
|
||||||
$list = id(new PHUIObjectItemListView())
|
$list = id(new PHUIObjectItemListView())
|
||||||
->setUser($viewer)
|
->setUser($viewer);
|
||||||
->setFlush(true);
|
|
||||||
foreach ($handles as $handle) {
|
foreach ($handles as $handle) {
|
||||||
$item = id(new PHUIObjectItemView())
|
$item = id(new PHUIObjectItemView())
|
||||||
->setHeader($handle->getFullName())
|
->setHeader($handle->getFullName())
|
||||||
|
|
|
@ -69,11 +69,12 @@ To update Phabricator, use a script like the one described in
|
||||||
@{article:Upgrading Phabricator}.
|
@{article:Upgrading Phabricator}.
|
||||||
|
|
||||||
**If you can not update** for some reason, please include the version of
|
**If you can not update** for some reason, please include the version of
|
||||||
Phabricator you are running in your report. The version is just the Git hash
|
Phabricator you are running when you file a report. You can find the version in
|
||||||
of your local HEAD. You can find the version by running `git show` in
|
{nav Config > Versions} in the web UI.
|
||||||
`phabricator/` and copy/pasting the first line of output, or by browsing to
|
|
||||||
{nav Config > All Settings} in the web UI and copy/pasting the information
|
(The version is just the Git hash of your local HEAD, so you can also find it
|
||||||
at the top.
|
by running `git show` in `phabricator/` and looking at the first line of
|
||||||
|
output.)
|
||||||
|
|
||||||
|
|
||||||
Supported Issues
|
Supported Issues
|
||||||
|
|
|
@ -114,7 +114,8 @@ We have a lot of users and a small team. Even if your feature is something we're
|
||||||
interested in and a good fit for where we want the product to go, it may take
|
interested in and a good fit for where we want the product to go, it may take
|
||||||
us a long time to get around to building it.
|
us a long time to get around to building it.
|
||||||
|
|
||||||
We work full time on Phabricator, and our long-term roadmap has many years worth
|
We work full time on Phabricator, and our long-term roadmap (which we call our
|
||||||
|
[[ https://secure.phabricator.com/w/starmap/ | Starmap ]]) has many years worth
|
||||||
of work. Your feature request is competing against thousands of other requests
|
of work. Your feature request is competing against thousands of other requests
|
||||||
for priority.
|
for priority.
|
||||||
|
|
||||||
|
@ -127,7 +128,8 @@ Even if your feature request is simple and has substantial impact for a large
|
||||||
number of users, the size of the request queue means that it is mathematically
|
number of users, the size of the request queue means that it is mathematically
|
||||||
unlikely to be near the top.
|
unlikely to be near the top.
|
||||||
|
|
||||||
You can find some information about how we prioritize in T4778. In particular,
|
You can find some information about how we prioritize in
|
||||||
|
[[ https://secure.phabricator.com/w/planning/ | Planning ]]. In particular,
|
||||||
we reprioritize frequently and can not accurately predict when we'll build a
|
we reprioritize frequently and can not accurately predict when we'll build a
|
||||||
feature which isn't very near to top of the queue.
|
feature which isn't very near to top of the queue.
|
||||||
|
|
||||||
|
@ -137,9 +139,9 @@ give you any updates or predictions about timelines. One day, out of nowhere,
|
||||||
your feature will materialize. That day may be a decade from now. You should
|
your feature will materialize. That day may be a decade from now. You should
|
||||||
have realistic expectations about this when filing a feature request.
|
have realistic expectations about this when filing a feature request.
|
||||||
|
|
||||||
If you want a concrete timeline, you can build the feature yourself. See
|
If you want a concrete timeline, you can work with us to pay for some control
|
||||||
@{article:Contributing Code} for details and alternatives to working with the
|
over our roadmap. For details, see
|
||||||
upstream.
|
[[ https://secure.phabricator.com/w/prioritization/ | Prioritization ]].
|
||||||
|
|
||||||
|
|
||||||
Describe Problems
|
Describe Problems
|
||||||
|
|
|
@ -50,19 +50,24 @@ just some intern who you don't trust) you can write a Herald rule to
|
||||||
automatically CC you on any revisions which match rules (like content, author,
|
automatically CC you on any revisions which match rules (like content, author,
|
||||||
files affected, etc.)
|
files affected, etc.)
|
||||||
|
|
||||||
= Differential Tips =
|
Inline Comments
|
||||||
|
===============
|
||||||
|
|
||||||
- You can leave inline comments by clicking the line numbers in the diff.
|
You can leave inline comments by clicking the line number next to a line. For
|
||||||
- You can leave a comment across multiple lines by dragging across the line
|
an in-depth look at inline comments, see
|
||||||
numbers.
|
@{article:Differential User Guide: Inline Comments}.
|
||||||
- Inline comments are initially saved as drafts. They are not submitted until
|
|
||||||
you submit a comment at the bottom of the page.
|
|
||||||
- Press "?" to view keyboard shortcuts.
|
|
||||||
|
|
||||||
= Next Steps =
|
|
||||||
|
|
||||||
- Read the FAQ at @{article:Differential User Guide: FAQ}; or
|
Next Steps
|
||||||
- learn about handling large changesets at
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- diving into the details of inline comments in
|
||||||
|
@{article:Differential User Guide: Inline Comments}; or
|
||||||
|
- reading the FAQ at @{article:Differential User Guide: FAQ}; or
|
||||||
|
- learning about handling large changesets in
|
||||||
@{article:Differential User Guide: Large Changes}; or
|
@{article:Differential User Guide: Large Changes}; or
|
||||||
- learn about test plans at @{article:Differential User Guide: Test Plans}; or
|
- learning about test plans in
|
||||||
- learn more about Herald at @{article:Herald User Guide}.
|
@{article:Differential User Guide: Test Plans}; or
|
||||||
|
- learning more about Herald in @{article:Herald User Guide}.
|
||||||
|
|
168
src/docs/user/userguide/differential_inlines.diviner
Normal file
168
src/docs/user/userguide/differential_inlines.diviner
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
@title Differential User Guide: Inline Comments
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Guide to inline comments in Differential.
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
Differential allows reviewers to leave feedback about changes to code inline,
|
||||||
|
within the body of the diff itself. These comments are called "inline
|
||||||
|
comments", and can be used to discuss specific parts of a change.
|
||||||
|
|
||||||
|
(NOTE) Click the line number next to a line to leave an inline comment.
|
||||||
|
|
||||||
|
To leave an inline comment, click the line number next to a line when reviewing
|
||||||
|
a change in Differential. You can also leave a comment on a range of adjacent
|
||||||
|
lines by clicking one line number and dragging to a nearby line number.
|
||||||
|
|
||||||
|
(NOTE) Other users can't see your comments right away!
|
||||||
|
|
||||||
|
When you make a comment, it is initially an **unsubmitted draft**, indicated by
|
||||||
|
an "Unsubmitted" badge in the comment header. Other users can't see it yet.
|
||||||
|
This behavior is different from the behavior of other software you may be
|
||||||
|
familiar with.
|
||||||
|
|
||||||
|
To publish your inline comments, scroll to the bottom of the page and submit
|
||||||
|
the form there. All your unsubmitted inline comments will be published when you
|
||||||
|
do, alongside an optional normal comment and optional action (like accepting
|
||||||
|
the revision).
|
||||||
|
|
||||||
|
Differential doesn't publish inlines initially because having a draft phase
|
||||||
|
gives reviewers more time to revise and adjust the inlines, and make their
|
||||||
|
feedback more cohesive, and contextualize their inlines with discussion in a
|
||||||
|
normal comment. It also allows Differential to send fewer, more relevant emails
|
||||||
|
and notifications.
|
||||||
|
|
||||||
|
|
||||||
|
Porting / Ghost Comments
|
||||||
|
========================
|
||||||
|
|
||||||
|
When a revision is updated, we attempt to port inline comments forward and
|
||||||
|
display them on the new diff. Ported comments have a pale, ghostly appearance
|
||||||
|
and include a button which allows you to jump back to the original context
|
||||||
|
where the comment appeared.
|
||||||
|
|
||||||
|
Ported comments sometimes appear in unexpected locations. There are two major
|
||||||
|
reasons for this:
|
||||||
|
|
||||||
|
- In the general case, it is not possible to always port comments to the same
|
||||||
|
lines humans would automatically.
|
||||||
|
- We are very aggressive about porting comments forward, even in the presence
|
||||||
|
of heavy changes. This helps prevent mistakes and makes it easier to verify
|
||||||
|
feedback has been addressed.
|
||||||
|
|
||||||
|
You can disable this behavior in
|
||||||
|
{nav Settings > Diff Preferences > Show Older Inlines} if you prefer comments
|
||||||
|
stay anchored to their original diff.
|
||||||
|
|
||||||
|
To understand why porting comments forward is difficult and can produce
|
||||||
|
unexpected results, and why we choose to be aggressive about it, let's look at
|
||||||
|
a case where the right behavior is ambiguous. Imagine this code is added as
|
||||||
|
part of a change:
|
||||||
|
|
||||||
|
```name=important.c
|
||||||
|
111 ...
|
||||||
|
112
|
||||||
|
113 if (a() || b() || c()) {
|
||||||
|
114 return;
|
||||||
|
115 }
|
||||||
|
116
|
||||||
|
117 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppose a reviewer leaves this comment on line 113:
|
||||||
|
|
||||||
|
> important.c:113 This is a serious security vulnerability!
|
||||||
|
|
||||||
|
The author later updates the diff, and the code now looks like this, with some
|
||||||
|
other changes elsewhere so the line numbers have also shifted:
|
||||||
|
|
||||||
|
```name=important.c
|
||||||
|
140 ...
|
||||||
|
141
|
||||||
|
142 if (a()) {
|
||||||
|
143 return;
|
||||||
|
144 }
|
||||||
|
145
|
||||||
|
146 if (b()) {
|
||||||
|
147 return;
|
||||||
|
148 }
|
||||||
|
149
|
||||||
|
150 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
If we port the inline forward from the first change to the second change, where
|
||||||
|
should it go? A human would probably do one of three things:
|
||||||
|
|
||||||
|
# Put it on line 142, with the call to `a()`.
|
||||||
|
# Put it on line 146, with the call to `b()`.
|
||||||
|
# Don't bring it forward at all.
|
||||||
|
|
||||||
|
A human would choose between (1) and (2) based on context about what `a()` and
|
||||||
|
`b()` are and what the reviewer meant. The algorithm can not possibly read the
|
||||||
|
comment and understand which part of the statement it talked about. Humans
|
||||||
|
might not even agree on which line is the better fit.
|
||||||
|
|
||||||
|
When we choose one of these behaviors, humans will sometimes think the other
|
||||||
|
behavior was the better one, because they have more information about what
|
||||||
|
`a()` and `b()` are and what the comment actually meant. The line we choose may
|
||||||
|
be unexpected, but it is the best the algorithm can do without being able to
|
||||||
|
actually read the code or understand what the comment means.
|
||||||
|
|
||||||
|
A human might also choose not to bring the comment forward if they knew that
|
||||||
|
removing `c()` addressed it, but the algorithm can not know that. The call to
|
||||||
|
`a()` or `b()` may be the dangerous thing the reviewer was talking about, and
|
||||||
|
we err on the side of caution by bringing comments forward aggressively.
|
||||||
|
|
||||||
|
When a line of code with an inline comment on it changes, we can not know if
|
||||||
|
the change addressed the inline or not. We take the cautious route and
|
||||||
|
//always// assume it did not, and that humans need to verify problems have
|
||||||
|
really been addressed.
|
||||||
|
|
||||||
|
This means that inlines are sometimes ported forward into places that don't
|
||||||
|
make sense (the code has changed completely), but we won't drop important
|
||||||
|
inlines just because the structure of the code has changed.
|
||||||
|
|
||||||
|
Here's another case where bringing inlines forward seems desirable. Imagine
|
||||||
|
this code is added as part of a change:
|
||||||
|
|
||||||
|
```name=warpgate.c
|
||||||
|
12 ...
|
||||||
|
13 function_x();
|
||||||
|
14 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppose a reviewer leaves this comment on line 13:
|
||||||
|
|
||||||
|
> warpgate.c:13 This should be function_y() instead.
|
||||||
|
|
||||||
|
The author later updates the diff, and the code now looks like this:
|
||||||
|
|
||||||
|
```name=warpgate.c
|
||||||
|
12 ...
|
||||||
|
13 function_y();
|
||||||
|
14 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
For the reasons discussed above, we port the comment forward, so it will appear
|
||||||
|
under line 13.
|
||||||
|
|
||||||
|
We think this is desirable: it makes it trivial for an author or reviewer to
|
||||||
|
look through the changes and verify that the feedback has really been
|
||||||
|
addressed, because you can see that the code now uses the proper function.
|
||||||
|
|
||||||
|
This isn't to say that we always do the best we can. There may be cases where
|
||||||
|
the algorithm can be smarter than it currently is or otherwise produce a better
|
||||||
|
result. If you find such a case, file a bug report. But it's expected that the
|
||||||
|
algorithm will sometimes port comments into places that aren't optimal or no
|
||||||
|
longer make sense, because this is frequently the best behavior available among
|
||||||
|
the possible alternatives.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- returning to the @{article:Differential User Guide}.
|
53
src/docs/user/userguide/differential_land.diviner
Normal file
53
src/docs/user/userguide/differential_land.diviner
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
@title Differential User Guide: Automated Landing
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Configuring Phabricator so you can "Land Revision" from the web UI.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
IMPORTANT: This feature is a prototype and has substantial limitations.
|
||||||
|
|
||||||
|
Phabricator can be configured so that approved revisions may be published
|
||||||
|
directly from the web interface. This can make publishing changes more
|
||||||
|
convenient, particularly for open source projects where authors may not have
|
||||||
|
commit access to the repository. This document explains the workflow and how to
|
||||||
|
configure it.
|
||||||
|
|
||||||
|
When properly configured, a {nav Land Revision} action will appear in
|
||||||
|
Differential. This action works like `arc land` on the command line, and
|
||||||
|
merges and publishes the revision.
|
||||||
|
|
||||||
|
This feature has significant limitations:
|
||||||
|
|
||||||
|
- This feature is a prototype.
|
||||||
|
- This feature is only supported in Git.
|
||||||
|
- This feature always lands changes onto `master`.
|
||||||
|
- This feature does not currently provide chain of custody, and what lands
|
||||||
|
may be arbitrarily different than what is shown in Differential.
|
||||||
|
|
||||||
|
To be landable, a revision must satisfy these requirements:
|
||||||
|
|
||||||
|
- It must belong to a repository which is tracked in Diffusion
|
||||||
|
(both hosted and imported repositories will work).
|
||||||
|
- The repository must have a **Staging Area** configured.
|
||||||
|
- The repository must have **Repository Automation** configured. For
|
||||||
|
details, see @{article:Drydock User Guide: Repository Automation}.
|
||||||
|
- The revision must have been created with `arc diff` and pushed to the
|
||||||
|
configured staging area at creation time.
|
||||||
|
- The user clicking the "Land Revision" button must have permission to push
|
||||||
|
to the repository.
|
||||||
|
|
||||||
|
If these requirements are met, the {nav Land Revision} action should be
|
||||||
|
available in the UI.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- configuring repository automation with
|
||||||
|
@{article:Drydock User Guide: Repository Automation}; or
|
||||||
|
- returning to the @{article:Differential User Guide}.
|
|
@ -15,16 +15,19 @@ applications coordinate during complex build and deployment tasks. Typically,
|
||||||
you will configure Drydock to enable capabilities in other applications:
|
you will configure Drydock to enable capabilities in other applications:
|
||||||
|
|
||||||
- Harbormaster can use Drydock to host builds.
|
- Harbormaster can use Drydock to host builds.
|
||||||
- In the future, Differential will be able to use Drydock to perform
|
- Differential can use Drydock to perform server-side merges.
|
||||||
server-side merges.
|
|
||||||
|
|
||||||
Users will not normally interact with Drydock directly.
|
Users will not normally interact with Drydock directly.
|
||||||
|
|
||||||
|
If you want to get started with Drydock right away, see
|
||||||
|
@{article:Drydock User Guide: Quick Start} for specific instructions on
|
||||||
|
configuring integrations.
|
||||||
|
|
||||||
|
|
||||||
What Drydock Does
|
What Drydock Does
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Drydock manages working copies, build hosts, and other software and hardware
|
Drydock manages working copies, hosts, and other software and hardware
|
||||||
resources that build and deployment processes may require in order to perform
|
resources that build and deployment processes may require in order to perform
|
||||||
useful work.
|
useful work.
|
||||||
|
|
||||||
|
@ -49,15 +52,202 @@ doing the actual work.
|
||||||
|
|
||||||
Drydock solves these scaling problems by providing a central allocation
|
Drydock solves these scaling problems by providing a central allocation
|
||||||
framework for //resources//, which are physical or virtual resources like a
|
framework for //resources//, which are physical or virtual resources like a
|
||||||
build host or a working copy. Processes which need to share hardware or
|
host or a working copy. Processes which need to share hardware or software can
|
||||||
software can use Drydock to coordinate creation, access, and destruction of
|
use Drydock to coordinate creation, access, and destruction of those resources.
|
||||||
those resources.
|
|
||||||
|
|
||||||
Applications ask Drydock for resources matching a description, and it allocates
|
Applications ask Drydock for resources matching a description, and it allocates
|
||||||
a corresponding resource by either finding a suitable unused resource or
|
a corresponding resource by either finding a suitable unused resource or
|
||||||
creating a new resource. When work completes, the resource is returned to the
|
creating a new resource. When work completes, the resource is returned to the
|
||||||
resource pool or destroyed.
|
resource pool or destroyed.
|
||||||
|
|
||||||
|
|
||||||
|
Getting Started with Drydock
|
||||||
|
============================
|
||||||
|
|
||||||
|
In general, you will interact with Drydock by configuring blueprints, which
|
||||||
|
tell Drydock how to build resources. You can jump into this topic directly
|
||||||
|
in @{article:Drydock Blueprints}.
|
||||||
|
|
||||||
|
For help on configuring specific application features:
|
||||||
|
|
||||||
|
- to configure server-side merges from Differential, see
|
||||||
|
@{article:Differential User Guide: Automated Landing}.
|
||||||
|
|
||||||
|
You should also understand the Drydock security model before deploying it
|
||||||
|
in a production environment. See @{article:Drydock User Guide: Security}.
|
||||||
|
|
||||||
|
The remainder of this document has some additional high-level discussion about
|
||||||
|
how Drydock works and why it works that way, which may be helpful in
|
||||||
|
understanding the application as a whole.
|
||||||
|
|
||||||
|
|
||||||
|
Drydock Concepts
|
||||||
|
================
|
||||||
|
|
||||||
|
The major concepts in Drydock are **Blueprints**, **Resources**, **Leases**,
|
||||||
|
and the **Allocator**.
|
||||||
|
|
||||||
|
**Blueprints** are configuration that tells Drydock how to create resources:
|
||||||
|
where it can put them, how to access them, how many it can make at once, who is
|
||||||
|
allowed to ask for access to them, how to actually build them, how to clean
|
||||||
|
them up when they are no longer in use, and so on.
|
||||||
|
|
||||||
|
Drydock starts without any blueprints. You'll add blueprints to configure
|
||||||
|
Drydock and enable it to satisfy requests for resources. You can learn more
|
||||||
|
about blueprints in @{article:Drydock Blueprints}.
|
||||||
|
|
||||||
|
**Resources** represent things (like hosts or working copies) that Drydock has
|
||||||
|
created, is managing the lifecycle for, and can give other applications access
|
||||||
|
to.
|
||||||
|
|
||||||
|
**Leases** are requests for resources with certain qualities by other
|
||||||
|
applications. For example, Harbormaster may request a working copy of a
|
||||||
|
particular repository so it can run unit tests.
|
||||||
|
|
||||||
|
The **Allocator** is where Drydock actually does work. It works roughly like
|
||||||
|
this:
|
||||||
|
|
||||||
|
- An application creates a lease describing a resource it needs, and
|
||||||
|
uses this lease to ask Drydock for an appropriate resource.
|
||||||
|
- Drydock looks at free resources to try to find one it can use to satisfy
|
||||||
|
the request. If it finds one, it marks the resource as in use and gives
|
||||||
|
the application details about how to access it.
|
||||||
|
- If it can't find an appropriate resource that already exists, it looks at
|
||||||
|
the blueprints it has configured to try to build one. If it can, it creates
|
||||||
|
a new resource, then gives the application access to it.
|
||||||
|
- Once the application finishes using the resource, it frees it. Depending
|
||||||
|
on configuration, Drydock may reuse it, destroy it, or hold onto it and
|
||||||
|
make a decision later.
|
||||||
|
|
||||||
|
Some minor concepts in Drydock are **Slot Locks** and **Repository Operations**.
|
||||||
|
|
||||||
|
**Slot Locks** are simple optimistic locks that most Drydock blueprints use to
|
||||||
|
avoid race conditions. Their design is not particularly interesting or novel,
|
||||||
|
they're just a fairly good fit for most of the locking problems that Drydock
|
||||||
|
blueprints tend to encounter and Drydock provides APIs to make them easy to
|
||||||
|
work with.
|
||||||
|
|
||||||
|
**Repository Operations** help other applications coordinate writes to
|
||||||
|
repositories. Multiple applications perform similar kinds of writes, and these
|
||||||
|
writes require more sequencing/coordination and user feedback than other
|
||||||
|
operations.
|
||||||
|
|
||||||
|
|
||||||
|
Architecture Overview
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This section describes some of Drydock's design goals and architectural
|
||||||
|
choices, so you can understand its strengths and weaknesses and which problem
|
||||||
|
domains it is well or poorly suited for.
|
||||||
|
|
||||||
|
A typical use case for Drydock is giving another application access to a
|
||||||
|
working copy in order to run a build or unit test operation. Drydock can
|
||||||
|
satisfy the request and resume execution of application code in 1-2 seconds
|
||||||
|
under reasonable conditions and with moderate tradeoffs, and can satisfy a
|
||||||
|
large number of these requests in parallel.
|
||||||
|
|
||||||
|
**Scalable**: Drydock is designed to scale easily to something in the realm of
|
||||||
|
thousands of hosts in hundreds of pools, and far beyond that with a little
|
||||||
|
work.
|
||||||
|
|
||||||
|
Drydock is intended to solve resource management problems at very large scales
|
||||||
|
and minimzes blocking operations, locks, and artificial sequencing. Drydock is
|
||||||
|
designed to fully utilize an almost arbitrarily large pool of resources and
|
||||||
|
improve performance roughly linearly with available hardware.
|
||||||
|
|
||||||
|
Because the application assumes that deployment at this scale and complexity
|
||||||
|
level is typical, you may need to configure more things and do more work than
|
||||||
|
you would under the simplifying assumptions of small scale.
|
||||||
|
|
||||||
|
**Heavy Resources**: Drydock assumes that resources are relatively
|
||||||
|
heavyweight and and require a meaningful amount (a second or more) of work to
|
||||||
|
build, maintain and tear down. It also assumes that leases will often have
|
||||||
|
substantial lifespans (seconds or minutes) while performing operations.
|
||||||
|
|
||||||
|
Resources like working copies (which typically take several seconds to create
|
||||||
|
with a command like `git clone`) and VMs (which typically take several seconds
|
||||||
|
to spin up) are good fits for Drydock and for the problems it is intended to
|
||||||
|
solve.
|
||||||
|
|
||||||
|
Lease operations like running unit tests, performing builds, executing merges,
|
||||||
|
generating documentation and running temporary services (which typically last
|
||||||
|
at least a few seconds) are also good fits for Drydock.
|
||||||
|
|
||||||
|
In both cases, the general concern with lightweight resources and operations is
|
||||||
|
that Drydock operation overhead is roughly on the order of a second for many
|
||||||
|
tasks, so overhead from Drydock will be substantial if resources are built and
|
||||||
|
torn down in a few milliseconds or lease operations require only a fraction of
|
||||||
|
a second to execute.
|
||||||
|
|
||||||
|
As a rule of thumb, Drydock may be a poor fit for a problem if operations
|
||||||
|
typically take less than a second to build, execute, and destroy.
|
||||||
|
|
||||||
|
**Focus on Resource Construction**: Drydock is primarily solving a resource
|
||||||
|
construction problem: something needs a resource matching some description, so
|
||||||
|
Drydock finds or builds that resource as quickly as possible.
|
||||||
|
|
||||||
|
Drydock generally prioritizes responding to requests quickly over other
|
||||||
|
concerns, like minimizing waste or performing complex scheduling. Although you
|
||||||
|
can make adjustments to some of these behaviors, it generally assumes that
|
||||||
|
resources are cheap compared to the cost of waiting for resource construction.
|
||||||
|
|
||||||
|
This isn't to say that Drydock is grossly wasteful or has a terrible scheduler,
|
||||||
|
just that efficient utilization and efficient scheduling aren't the primary
|
||||||
|
problems the design focuses on.
|
||||||
|
|
||||||
|
This prioritization corresponds to scenarios where resources are something like
|
||||||
|
hosts or working copies, and operations are something like builds, and the cost
|
||||||
|
of hosts and storage is small compared to the cost of engineer time spent
|
||||||
|
waiting on jobs to get scheduled.
|
||||||
|
|
||||||
|
Drydock may be a weak fit for a problem if it is bounded by resource
|
||||||
|
availability and using resources as efficiently as possible is very important.
|
||||||
|
Drydock generally assumes you will respond to a resource deficit by making more
|
||||||
|
resources available (usually very cheap), rather than by paying engineers to
|
||||||
|
wait for operations to complete (usually very expensive).
|
||||||
|
|
||||||
|
**Isolation Tradeoffs**: Drydock assumes that multiple operations running at
|
||||||
|
similar levels of trust may be interested in reducing isolation to improve
|
||||||
|
performance, reduce complexity, or satisfy some other similar goal. It does not
|
||||||
|
guarantee isolation and assumes most operations will not run in total isolation.
|
||||||
|
|
||||||
|
If this isn't true for your use case, you'll need to be careful in configuring
|
||||||
|
Drydock to make sure that operations are fully isolated and can not interact.
|
||||||
|
Complete isolation will reduce the performance of the allocator as it will
|
||||||
|
generally prevent it from reusing resources, which is one of the major ways it
|
||||||
|
can improve performance.
|
||||||
|
|
||||||
|
You can find more discussion of these tradeoffs in
|
||||||
|
@{article:Drydock User Guide: Security}.
|
||||||
|
|
||||||
|
**Agentless**: Drydock does not require an agent or daemon to be installed on
|
||||||
|
hosts. It interacts with hosts over SSH.
|
||||||
|
|
||||||
|
**Very Abstract**: Drydock's design is //extremely// abstract. Resources have
|
||||||
|
very little hardcoded behavior. The allocator has essentially zero specialized
|
||||||
|
knowledge about what it is actually doing.
|
||||||
|
|
||||||
|
One aspect of this abstractness is that Drydock is composable, and solves
|
||||||
|
complex allocation problems by //asking itself// to build the pieces it needs.
|
||||||
|
To build a working copy, Drydock first asks itself for a suitable host. It
|
||||||
|
solves this allocation sub-problem, then resolves the original request.
|
||||||
|
|
||||||
|
This allows new types of resources to build on Drydock's existing knowledge of
|
||||||
|
resource construction by just saying "build one of these other things you
|
||||||
|
already know how to build, then apply a few adjustments". This also means that
|
||||||
|
you can tell Drydock about a new way to build hosts (say, bring up VMs from a
|
||||||
|
different service provider) and the rest of the pipeline can use these new
|
||||||
|
hosts interchangeably with the old hosts.
|
||||||
|
|
||||||
|
While this design theoretically makes Drydock more powerful and more flexible
|
||||||
|
than a less abstract approach, abstraction is frequently a double-edged sword.
|
||||||
|
|
||||||
|
Drydock is almost certainly at the extreme upper end of abstraction for tools
|
||||||
|
in this space, and the level of abstraction may ultimately match poorly with a
|
||||||
|
particular problem domain. Alternative approaches may give you more specialized
|
||||||
|
and useful tools for approaching a given problem.
|
||||||
|
|
||||||
|
|
||||||
Next Steps
|
Next Steps
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
@ -65,5 +255,6 @@ Continue by:
|
||||||
|
|
||||||
- understanding Drydock security concerns with
|
- understanding Drydock security concerns with
|
||||||
@{article:Drydock User Guide: Security}; or
|
@{article:Drydock User Guide: Security}; or
|
||||||
|
- learning about blueprints in @{article:Drydock Blueprints}; or
|
||||||
- allowing Phabricator to write to repositories with
|
- allowing Phabricator to write to repositories with
|
||||||
@{article:Drydock User Guide: Repository Automation}.
|
@{article:Drydock User Guide: Repository Automation}.
|
||||||
|
|
80
src/docs/user/userguide/drydock_blueprints.diviner
Normal file
80
src/docs/user/userguide/drydock_blueprints.diviner
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
@title Drydock Blueprints
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Overview of Drydock blueprint types.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
IMPORTANT: Drydock is not a mature application and may be difficult to
|
||||||
|
configure and use for now.
|
||||||
|
|
||||||
|
Drydock builds and manages various hardware and software resources, like
|
||||||
|
hosts and repository working copies. Other applications can use these resources
|
||||||
|
to perform useful work (like running tests or builds).
|
||||||
|
|
||||||
|
For additional disussion of Drydock, see @{article:Drydock User Guide}.
|
||||||
|
|
||||||
|
Drydock can't create any resources until you configure it. You'll configure
|
||||||
|
Drydock by creating **Blueprints**. Each blueprint tells Drydock how to build
|
||||||
|
a specific kind of resource, how many it is allowed to build, where it should
|
||||||
|
build them, who is authorized to request them, and so on.
|
||||||
|
|
||||||
|
You can create a new blueprint in Drydock from the web UI:
|
||||||
|
|
||||||
|
{nav Drydock > Blueprints > New Blueprint}
|
||||||
|
|
||||||
|
Each blueprint builds resources of a specific type, like hosts or repository
|
||||||
|
working copies. Detailed topic guides are available for each resource type:
|
||||||
|
|
||||||
|
**Hosts**: Hosts are the building block for most other resources. For details,
|
||||||
|
see @{article:Drydock Blueprints: Hosts}.
|
||||||
|
|
||||||
|
**Working Copies**: Working copies allow Drydock to perform repository
|
||||||
|
operations like running tests, performing builds, and handling merges.
|
||||||
|
|
||||||
|
|
||||||
|
Authorizing Access
|
||||||
|
==================
|
||||||
|
|
||||||
|
Before objects in other applications can use a blueprint, the blueprint must
|
||||||
|
authorize them.
|
||||||
|
|
||||||
|
This mostly serves to prevent users with limited access from executing
|
||||||
|
operations on trusted hosts. For additional discussion, see
|
||||||
|
@{article:Drydock User Guide: Security}.
|
||||||
|
|
||||||
|
This also broadly prevents Drydock from surprising you by coming up with a
|
||||||
|
valid but unintended solution to an allocation problem which runs some
|
||||||
|
operation on resources that are techincally suitable but not desirable. For
|
||||||
|
example, you may not want your Android builds running on your iPhone build
|
||||||
|
tier, even if there's no technical reason they can't.
|
||||||
|
|
||||||
|
You can review active authorizations and pending authorization requests in
|
||||||
|
the "Active Authorizations" section of the blueprint detail screen.
|
||||||
|
|
||||||
|
To approve an authorization, click it and select {nav Approve Authorization}.
|
||||||
|
Until you do, the requesting object won't be able to access resources from
|
||||||
|
the blueprint.
|
||||||
|
|
||||||
|
You can also decline an authorization. This prevents use of resources and
|
||||||
|
removes it from the authorization approval queue.
|
||||||
|
|
||||||
|
|
||||||
|
Disabling Blueprints
|
||||||
|
====================
|
||||||
|
|
||||||
|
You can disable a blueprint by selecting {nav Disable Blueprint} from the
|
||||||
|
blueprint detail screen.
|
||||||
|
|
||||||
|
Disabled blueprints will no longer be used for new allocations. However,
|
||||||
|
existing resources will continue to function.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- returning to the @{article:Drydock User Guide}.
|
126
src/docs/user/userguide/drydock_hosts.diviner
Normal file
126
src/docs/user/userguide/drydock_hosts.diviner
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
@title Drydock Blueprints: Hosts
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Guide to configuring Drydock host blueprints.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
IMPORTANT: Drydock is not a mature application and may be difficult to
|
||||||
|
configure and use for now.
|
||||||
|
|
||||||
|
To give Drydock access to machines so it can perform work, you'll configure
|
||||||
|
**host blueprints**. These blueprints tell Drydock where to find machines (or
|
||||||
|
how to build machines) and how to connect to them.
|
||||||
|
|
||||||
|
Once Drydock has access to hosts it can use them to build more interesting and
|
||||||
|
complex types of resources, like repository working copies.
|
||||||
|
|
||||||
|
Drydock currently supports these kinds of host blueprints:
|
||||||
|
|
||||||
|
- **Almanac Hosts**: Gives Drydock access to a predefined list of hosts.
|
||||||
|
|
||||||
|
Drydock may support additional blueprints in the future.
|
||||||
|
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
Drydock can be used to run semi-trusted and untrusted code, and you may want
|
||||||
|
to isolate specific processes or classes of processes from one another. See
|
||||||
|
@{article:Drydock User Guide: Security} for discussion of security
|
||||||
|
concerns and guidance on how to make isolation tradeoffs.
|
||||||
|
|
||||||
|
|
||||||
|
General Considerations
|
||||||
|
======================
|
||||||
|
|
||||||
|
**You must install software on hosts.** Drydock does not currently handle
|
||||||
|
installing software on hosts. You'll need to make sure any hosts are configured
|
||||||
|
properly with any software you need, and have tools like `git`, `hg` or `svn`
|
||||||
|
that may be required to interact with working copies.
|
||||||
|
|
||||||
|
You do **not** need to install PHP, arcanist, libphutil or Phabricator on the
|
||||||
|
hosts unless you are specifically running `arc` commands.
|
||||||
|
|
||||||
|
**You must configure authentication.** Drydock also does not handle credentials
|
||||||
|
for VCS operations. If you're interacting with repositories hosted on
|
||||||
|
Phabricator, the simplest way to set this up is something like this:
|
||||||
|
|
||||||
|
- Create a new bot user in Phabricator.
|
||||||
|
- In {nav Settings > SSH Public Keys}, add a public key or generate a
|
||||||
|
keypair.
|
||||||
|
- Put the private key on your build hosts as `~/.ssh/id_rsa` for whatever
|
||||||
|
user you're connecting with.
|
||||||
|
|
||||||
|
This will let processes on the host access Phabricator as the bot user, and
|
||||||
|
use the bot user's permissions to pull and push changes.
|
||||||
|
|
||||||
|
If you're using hosted repositories from an external service, you can follow
|
||||||
|
similar steps for that service.
|
||||||
|
|
||||||
|
Note that any processes running under the given user account will have access
|
||||||
|
to the private key, so you should give the bot the smallest acceptable level of
|
||||||
|
permissions if you're running semi-trusted or untrusted code like unit tests.
|
||||||
|
|
||||||
|
**You must create a `/var/drydock` directory.** This is hard-coded in Drydock
|
||||||
|
for now, so you need to create it on the hosts. This can be a symlink to
|
||||||
|
a different location if you prefer.
|
||||||
|
|
||||||
|
|
||||||
|
Almanac Hosts
|
||||||
|
=============
|
||||||
|
|
||||||
|
The **Almanac Hosts** blueprint type gives Drydock access to a predefined list
|
||||||
|
of hosts which you configure in the Almanac application. This is the simplest
|
||||||
|
type of blueprint to set up.
|
||||||
|
|
||||||
|
For more information about Almanac, see @{article:Almanac User Guide}.
|
||||||
|
|
||||||
|
For example, suppose you have `build001.mycompany.com` and
|
||||||
|
`build002.mycompany.com`, and want to configure Drydock to be able to use these
|
||||||
|
hosts. To do this:
|
||||||
|
|
||||||
|
**Create Almanac Devices**: Create a device record in Almanac for each your
|
||||||
|
hosts.
|
||||||
|
|
||||||
|
{nav Almanac > Devices > Create Device}
|
||||||
|
|
||||||
|
Enter the device names (like `build001.mycompany.com`). After creating the
|
||||||
|
devices, use {nav Add Interface} to configure the ports and IP addresses that
|
||||||
|
Drydock should connect to over SSH (normally, this is port `22`).
|
||||||
|
|
||||||
|
**Create an Almanac Service**: In the Almanac application, create a new service
|
||||||
|
to define the pool of devices you want to use.
|
||||||
|
|
||||||
|
{nav Almanac > Services > Create Service}
|
||||||
|
|
||||||
|
Choose the service type **Drydock: Resource Pool**. This will allow Drydock
|
||||||
|
to use the devices that are bound to the service.
|
||||||
|
|
||||||
|
Now, use {nav Add Binding} to bind all of the devices to the service.
|
||||||
|
|
||||||
|
You can add more hosts to the pool later by binding additional devices, and
|
||||||
|
Drydock will automatically start using them. Likewise, you can remove bindings
|
||||||
|
to take hosts out of service.
|
||||||
|
|
||||||
|
**Create a Drydock Blueprint**: Now, create a new blueprint in Drydock.
|
||||||
|
|
||||||
|
{nav Drydock > Blueprints > New Blueprint}
|
||||||
|
|
||||||
|
Choose the **Almanac Hosts** blueprint type.
|
||||||
|
|
||||||
|
In **Almanac Services**, select the service you previously created. For
|
||||||
|
**Credentials**, select an SSH private key you want Drydock to use to connect
|
||||||
|
to the hosts.
|
||||||
|
|
||||||
|
Drydock should now be able to build resources from these hosts.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- returning to @{article:Drydock Blueprints}.
|
74
src/docs/user/userguide/drydock_quick_start.diviner
Normal file
74
src/docs/user/userguide/drydock_quick_start.diviner
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
@title Drydock User Guide: Quick Start
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Guide to getting Drydock
|
||||||
|
|
||||||
|
Quick Start: Land Revisions
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Quick start guide to getting "Land Revision" working in Differential. For
|
||||||
|
a more detailed guide, see @{article:Drydock User Guide: Repository Automation}.
|
||||||
|
|
||||||
|
Choose a repository you want to enable "Land Revision" for. We'll call this
|
||||||
|
**Repository X**.
|
||||||
|
|
||||||
|
You need to configure a staging area for this repository if you haven't
|
||||||
|
already. You can do this in Diffusion in {nav Edit Repository > Edit Staging}.
|
||||||
|
We'll call this **Staging Area Y**.
|
||||||
|
|
||||||
|
Choose or create a host you want to run merges on. We'll call this
|
||||||
|
`automation001`. For example, you might bring up a new host in EC2 and
|
||||||
|
label it `automation001.mycompany.com`. You can use an existing host if you
|
||||||
|
prefer.
|
||||||
|
|
||||||
|
Create a user account on the host, or choose an existing user account. This is
|
||||||
|
the user that merges will execute under: Drydock will connect to it and run a
|
||||||
|
bunch of `git` commands, then ultimately run `git push`. We'll call this user
|
||||||
|
`builder`.
|
||||||
|
|
||||||
|
Install `git`, `hg` or `svn` if you haven't already and set up private keys
|
||||||
|
for `builder` so it can pull and push any repositories you want to operate
|
||||||
|
on.
|
||||||
|
|
||||||
|
If your repository and/or staging area are hosted in Phabricator, you may want
|
||||||
|
to create a corresponding bot account so you can add keys and give it
|
||||||
|
permissions.
|
||||||
|
|
||||||
|
At this point you should be able to `ssh builder@automation001` to connect to
|
||||||
|
the host, and get a normal shell. You should be able to `git clone ...` from
|
||||||
|
**Repository X** and from **Staging Area Y**, and `git push` to **Repository
|
||||||
|
X**. If you can't, configure things so you can.
|
||||||
|
|
||||||
|
Now, create a host blueprint for the host. You can find a more detailed
|
||||||
|
walkthrough in @{article:Drydock Blueprints: Hosts}. Briefly:
|
||||||
|
|
||||||
|
- Create an Almanac device for the host. This should have the IP address and
|
||||||
|
port for your host.
|
||||||
|
- Create an Almanac service bound to the device. This should be a Drydock
|
||||||
|
resource pool service and have a binding to the IP from the previous step.
|
||||||
|
- Create a Drydock host blueprint which uses the service from the previous
|
||||||
|
step. It should be configured with an SSH private key that can be used
|
||||||
|
to connect to `builder@automation001`.
|
||||||
|
|
||||||
|
Then, create a new working copy blueprint which uses the host blueprint you
|
||||||
|
just made. You can find a more detailed walkthrough in @{article:Drydock
|
||||||
|
Blueprints: Working Copies}. Authorize the working copy blueprint to use the
|
||||||
|
host blueprint.
|
||||||
|
|
||||||
|
Finally, configure repository automation for **Repository X**:
|
||||||
|
{nav Edit Repository > Edit Automation}. Provide the working copy blueprint
|
||||||
|
from the previous step. Authorize the repository to use the working copy
|
||||||
|
blueprint.
|
||||||
|
|
||||||
|
After you save changes, click {nav Test Configuration} to test that things
|
||||||
|
are working properly.
|
||||||
|
|
||||||
|
The "Land Revision" action should now be available on revisions for this
|
||||||
|
repository.
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- returning to @{article:Drydock User Guide}.
|
|
@ -7,12 +7,19 @@ Configuring repository automation so Phabricator can push commits.
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
IMPORTANT: This feature is very new and most of the capabilities described
|
IMPORTANT: This feature is very new and some of the capabilities described
|
||||||
in this document are not yet available. This feature as a whole is a prototype.
|
in this document are not yet available. This feature as a whole is a prototype.
|
||||||
|
|
||||||
By configuring Drydock and Diffusion appropriately, you can enable **Repository
|
By configuring Drydock and Diffusion appropriately, you can enable **Repository
|
||||||
Automation** for a repository. Once automation is set up, Phabricator will be
|
Automation** for a repository. This will allow Phabricator to make changes
|
||||||
able to make changes to the repository.
|
to the repository.
|
||||||
|
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
===========
|
||||||
|
|
||||||
|
- This feature is a prototype.
|
||||||
|
- Only Git is supported.
|
||||||
|
|
||||||
|
|
||||||
Security
|
Security
|
||||||
|
@ -29,6 +36,45 @@ with automation. You can read more about this in
|
||||||
@{article:Drydock User Guide: Security}.
|
@{article:Drydock User Guide: Security}.
|
||||||
|
|
||||||
|
|
||||||
|
Configuring Automation
|
||||||
|
======================
|
||||||
|
|
||||||
|
To configure automation, use {nav Edit Repository > Edit Automation} from
|
||||||
|
Diffusion.
|
||||||
|
|
||||||
|
On the configuration screen, specify one or more working copy blueprints in
|
||||||
|
Drydock (usually, you'll just use one). Repository automation will use working
|
||||||
|
copies built by these blueprints to perform merges and push changes.
|
||||||
|
|
||||||
|
For more details on configuring these blueprints, see
|
||||||
|
@{article:Drydock Blueprints: Working Copies}.
|
||||||
|
|
||||||
|
After selecting one or more blueprints, make sure you authorize the repository
|
||||||
|
to use them. Automation operations won't be able to proceed until you do. The
|
||||||
|
UI will remind you if you have unauthorized blueprints selected.
|
||||||
|
|
||||||
|
|
||||||
|
Testing Configuration
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Once the blueprints are configured and authorized, use {nav Test Configuration}
|
||||||
|
to check that things are configured correctly. This will build a working copy
|
||||||
|
in Drydock, connect to it, and run a trivial command (like `git status`) to
|
||||||
|
make sure things work.
|
||||||
|
|
||||||
|
If it's the first time you're doing this, it may take a few moments since it
|
||||||
|
will need to clone a fresh working copy.
|
||||||
|
|
||||||
|
If the test is successful, your configuration is generally in good shape. If
|
||||||
|
not, it should give you more details about what went wrong.
|
||||||
|
|
||||||
|
Since the test doesn't actually do a push, it's possible that you may have
|
||||||
|
everything configured properly //except// write access. In this case, you'll
|
||||||
|
run into a permission error when you try to actually perform a merge or other
|
||||||
|
similar write. If you do, adjust permissions or credentials appropriately so
|
||||||
|
the working copy can be pushed from.
|
||||||
|
|
||||||
|
|
||||||
Next Steps
|
Next Steps
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
|
39
src/docs/user/userguide/drydock_working_copies.diviner
Normal file
39
src/docs/user/userguide/drydock_working_copies.diviner
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
@title Drydock Blueprints: Working Copies
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Guide to configuring Drydock working copy blueprints.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
IMPORTANT: Drydock is not a mature application and may be difficult to
|
||||||
|
configure and use for now.
|
||||||
|
|
||||||
|
To let Drydock build repository working copies in order to run unit tests and
|
||||||
|
other similar operations, you'll configure **working copy blueprints**.
|
||||||
|
|
||||||
|
Working Copies
|
||||||
|
==============
|
||||||
|
|
||||||
|
Working copy blueprints rely on host blueprints, so you'll need to configure
|
||||||
|
a suitable host blueprint first. See @{article:Drydock Blueprints: Hosts}.
|
||||||
|
|
||||||
|
To configure a working copy blueprint, choose the host blueprints it should
|
||||||
|
use in **Use Blueprints**.
|
||||||
|
|
||||||
|
You can optionally specify a **Limit**. If you do, the blueprint won't be
|
||||||
|
allowed to create more than this many simultaneous resources. If you leave
|
||||||
|
it empty, the blueprint will be able to create an unlimited number of
|
||||||
|
resources.
|
||||||
|
|
||||||
|
After you save the blueprint, make sure you authorize it to use the selected
|
||||||
|
host blueprints. It won't be able to acquire host resources until you do.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- returning to @{article:Drydock Blueprints}.
|
|
@ -496,6 +496,12 @@ For a list of available icons and colors, check the UIExamples application.
|
||||||
[[ http://fortawesome.github.io/Font-Awesome/ | FontAwesome ]], so you can also
|
[[ http://fortawesome.github.io/Font-Awesome/ | FontAwesome ]], so you can also
|
||||||
browse the collection there.)
|
browse the collection there.)
|
||||||
|
|
||||||
|
You can add `spin` to make the icon spin:
|
||||||
|
|
||||||
|
{icon cog spin}
|
||||||
|
|
||||||
|
This renders: {icon cog spin}
|
||||||
|
|
||||||
|
|
||||||
= Phriction Documents =
|
= Phriction Documents =
|
||||||
|
|
||||||
|
|
|
@ -1420,6 +1420,11 @@ final class PhabricatorUSEnglishTranslation
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'WARNING: There are %s unapproved authorization(s)!' => array(
|
||||||
|
'WARNING: There is an unapproved authorization!',
|
||||||
|
'WARNING: There are unapproved authorizations!',
|
||||||
|
),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ final class PhabricatorStorageManagementAPI extends Phobject {
|
||||||
const COLLATE_SORT = 'COLLATE_SORT';
|
const COLLATE_SORT = 'COLLATE_SORT';
|
||||||
const COLLATE_FULLTEXT = 'COLLATE_FULLTEXT';
|
const COLLATE_FULLTEXT = 'COLLATE_FULLTEXT';
|
||||||
|
|
||||||
|
const TABLE_STATUS = 'patch_status';
|
||||||
|
|
||||||
public function setDisableUTF8MB4($disable_utf8_mb4) {
|
public function setDisableUTF8MB4($disable_utf8_mb4) {
|
||||||
$this->disableUTF8MB4 = $disable_utf8_mb4;
|
$this->disableUTF8MB4 = $disable_utf8_mb4;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -118,13 +120,26 @@ final class PhabricatorStorageManagementAPI extends Phobject {
|
||||||
try {
|
try {
|
||||||
$applied = queryfx_all(
|
$applied = queryfx_all(
|
||||||
$this->getConn('meta_data'),
|
$this->getConn('meta_data'),
|
||||||
'SELECT patch FROM patch_status');
|
'SELECT patch FROM %T',
|
||||||
|
self::TABLE_STATUS);
|
||||||
return ipull($applied, 'patch');
|
return ipull($applied, 'patch');
|
||||||
} catch (AphrontQueryException $ex) {
|
} catch (AphrontQueryException $ex) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPatchDurations() {
|
||||||
|
try {
|
||||||
|
$rows = queryfx_all(
|
||||||
|
$this->getConn('meta_data'),
|
||||||
|
'SELECT patch, duration FROM %T WHERE duration IS NOT NULL',
|
||||||
|
self::TABLE_STATUS);
|
||||||
|
return ipull($rows, 'duration', 'patch');
|
||||||
|
} catch (AphrontQueryException $ex) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function createDatabase($fragment) {
|
public function createDatabase($fragment) {
|
||||||
$info = $this->getCharsetInfo();
|
$info = $this->getCharsetInfo();
|
||||||
|
|
||||||
|
@ -168,13 +183,30 @@ final class PhabricatorStorageManagementAPI extends Phobject {
|
||||||
return $legacy;
|
return $legacy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markPatchApplied($patch) {
|
public function markPatchApplied($patch, $duration = null) {
|
||||||
|
$conn = $this->getConn('meta_data');
|
||||||
|
|
||||||
queryfx(
|
queryfx(
|
||||||
$this->getConn('meta_data'),
|
$conn,
|
||||||
'INSERT INTO %T (patch, applied) VALUES (%s, %d)',
|
'INSERT INTO %T (patch, applied) VALUES (%s, %d)',
|
||||||
'patch_status',
|
self::TABLE_STATUS,
|
||||||
$patch,
|
$patch,
|
||||||
time());
|
time());
|
||||||
|
|
||||||
|
// We didn't add this column for a long time, so it may not exist yet.
|
||||||
|
if ($duration !== null) {
|
||||||
|
try {
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'UPDATE %T SET duration = %d WHERE patch = %s',
|
||||||
|
self::TABLE_STATUS,
|
||||||
|
(int)floor($duration * 1000000),
|
||||||
|
$patch);
|
||||||
|
} catch (AphrontQueryException $ex) {
|
||||||
|
// Just ignore this, as it almost certainly indicates that we just
|
||||||
|
// don't have the column yet.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applyPatch(PhabricatorStoragePatch $patch) {
|
public function applyPatch(PhabricatorStoragePatch $patch) {
|
||||||
|
|
|
@ -29,15 +29,26 @@ final class PhabricatorStorageManagementStatusWorkflow
|
||||||
->setShowHeader(false)
|
->setShowHeader(false)
|
||||||
->addColumn('id', array('title' => pht('ID')))
|
->addColumn('id', array('title' => pht('ID')))
|
||||||
->addColumn('status', array('title' => pht('Status')))
|
->addColumn('status', array('title' => pht('Status')))
|
||||||
|
->addColumn('duration', array('title' => pht('Duration')))
|
||||||
->addColumn('type', array('title' => pht('Type')))
|
->addColumn('type', array('title' => pht('Type')))
|
||||||
->addColumn('name', array('title' => pht('Name')));
|
->addColumn('name', array('title' => pht('Name')));
|
||||||
|
|
||||||
|
$durations = $api->getPatchDurations();
|
||||||
|
|
||||||
foreach ($patches as $patch) {
|
foreach ($patches as $patch) {
|
||||||
|
$duration = idx($durations, $patch->getFullKey());
|
||||||
|
if ($duration === null) {
|
||||||
|
$duration = '-';
|
||||||
|
} else {
|
||||||
|
$duration = pht('%s us', new PhutilNumber($duration));
|
||||||
|
}
|
||||||
|
|
||||||
$table->addRow(array(
|
$table->addRow(array(
|
||||||
'id' => $patch->getFullKey(),
|
'id' => $patch->getFullKey(),
|
||||||
'status' => in_array($patch->getFullKey(), $applied)
|
'status' => in_array($patch->getFullKey(), $applied)
|
||||||
? pht('Applied')
|
? pht('Applied')
|
||||||
: pht('Not Applied'),
|
: pht('Not Applied'),
|
||||||
|
'duration' => $duration,
|
||||||
'type' => $patch->getType(),
|
'type' => $patch->getType(),
|
||||||
'name' => $patch->getName(),
|
'name' => $patch->getName(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -187,9 +187,13 @@ final class PhabricatorStorageManagementUpgradeWorkflow
|
||||||
echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n";
|
echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n";
|
||||||
} else {
|
} else {
|
||||||
echo pht("Applying patch '%s'...", $key)."\n";
|
echo pht("Applying patch '%s'...", $key)."\n";
|
||||||
|
|
||||||
|
$t_begin = microtime(true);
|
||||||
$api->applyPatch($patch);
|
$api->applyPatch($patch);
|
||||||
|
$t_end = microtime(true);
|
||||||
|
|
||||||
if (!$skip_mark) {
|
if (!$skip_mark) {
|
||||||
$api->markPatchApplied($key);
|
$api->markPatchApplied($key, ($t_end - $t_begin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ final class PhabricatorStorageSchemaSpec
|
||||||
array(
|
array(
|
||||||
'patch' => 'text128',
|
'patch' => 'text128',
|
||||||
'applied' => 'uint32',
|
'applied' => 'uint32',
|
||||||
|
'duration' => 'uint64?',
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'PRIMARY' => array(
|
'PRIMARY' => array(
|
||||||
|
|
|
@ -23,6 +23,7 @@ final class AphrontDialogView
|
||||||
private $errors = array();
|
private $errors = array();
|
||||||
private $flush;
|
private $flush;
|
||||||
private $validationException;
|
private $validationException;
|
||||||
|
private $objectList;
|
||||||
|
|
||||||
|
|
||||||
const WIDTH_DEFAULT = 'default';
|
const WIDTH_DEFAULT = 'default';
|
||||||
|
@ -132,6 +133,13 @@ final class AphrontDialogView
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setObjectList(PHUIObjectItemListView $list) {
|
||||||
|
$this->objectList = true;
|
||||||
|
$box = id(new PHUIObjectBoxView())
|
||||||
|
->setObjectList($list);
|
||||||
|
return $this->appendChild($box);
|
||||||
|
}
|
||||||
|
|
||||||
public function appendParagraph($paragraph) {
|
public function appendParagraph($paragraph) {
|
||||||
return $this->appendChild(
|
return $this->appendChild(
|
||||||
phutil_tag(
|
phutil_tag(
|
||||||
|
@ -236,15 +244,17 @@ final class AphrontDialogView
|
||||||
__CLASS__));
|
__CLASS__));
|
||||||
}
|
}
|
||||||
|
|
||||||
$more = $this->class;
|
$classes = array();
|
||||||
|
$classes[] = 'aphront-dialog-view';
|
||||||
|
$classes[] = $this->class;
|
||||||
if ($this->flush) {
|
if ($this->flush) {
|
||||||
$more .= ' aphront-dialog-flush';
|
$classes[] = 'aphront-dialog-flush';
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->width) {
|
switch ($this->width) {
|
||||||
case self::WIDTH_FORM:
|
case self::WIDTH_FORM:
|
||||||
case self::WIDTH_FULL:
|
case self::WIDTH_FULL:
|
||||||
$more .= ' aphront-dialog-view-width-'.$this->width;
|
$classes[] = 'aphront-dialog-view-width-'.$this->width;
|
||||||
break;
|
break;
|
||||||
case self::WIDTH_DEFAULT:
|
case self::WIDTH_DEFAULT:
|
||||||
break;
|
break;
|
||||||
|
@ -256,11 +266,15 @@ final class AphrontDialogView
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isStandalone) {
|
if ($this->isStandalone) {
|
||||||
$more .= ' aphront-dialog-view-standalone';
|
$classes[] = 'aphront-dialog-view-standalone';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->objectList) {
|
||||||
|
$classes[] = 'aphront-dialog-object-list';
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes = array(
|
$attributes = array(
|
||||||
'class' => 'aphront-dialog-view '.$more,
|
'class' => implode(' ', $classes),
|
||||||
'sigil' => 'jx-dialog',
|
'sigil' => 'jx-dialog',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,7 @@ final class PHUIPropertyListView extends AphrontView {
|
||||||
// If we have an action list, make sure we render a property part, even
|
// If we have an action list, make sure we render a property part, even
|
||||||
// if there are no properties. Otherwise, the action list won't render.
|
// if there are no properties. Otherwise, the action list won't render.
|
||||||
if ($this->actionList) {
|
if ($this->actionList) {
|
||||||
|
$this->classes[] = 'phui-property-list-has-actions';
|
||||||
$have_property_part = false;
|
$have_property_part = false;
|
||||||
foreach ($this->parts as $part) {
|
foreach ($this->parts as $part) {
|
||||||
if ($part['type'] == 'property') {
|
if ($part['type'] == 'property') {
|
||||||
|
|
|
@ -128,3 +128,13 @@
|
||||||
border: 0;
|
border: 0;
|
||||||
border-top: 1px solid {$thinblueborder};
|
border-top: 1px solid {$thinblueborder};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aphront-dialog-object-list .phui-object-box {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aphront-dialog-object-list .aphront-dialog-body {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ th.aphront-table-view-sortable-selected {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: {$bluetext};
|
color: {$bluetext};
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aphront-table-view td {
|
.aphront-table-view td {
|
||||||
|
|
|
@ -3,13 +3,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.harbormaster-artifact-io {
|
.harbormaster-artifact-io {
|
||||||
margin: 0 0 0 8px;
|
margin: 4px 0 4px 8px;
|
||||||
padding: 4px 8px;
|
padding: 8px;
|
||||||
border-width: 1px 0 0 1px;
|
|
||||||
border-style: solid;
|
|
||||||
box-shadow: inset 2px 2px 1px rgba(0, 0, 0, 0.075);
|
|
||||||
background: {$lightbluebackground};
|
|
||||||
border-color: {$lightblueborder};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.harbormaster-artifact-summary-header {
|
.harbormaster-artifact-summary-header {
|
||||||
|
|
|
@ -334,6 +334,10 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phabricator-remarkup .remarkup-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.phabricator-remarkup table.remarkup-table {
|
.phabricator-remarkup table.remarkup-table {
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
border-spacing: 1px;
|
border-spacing: 1px;
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-desktop .phui-property-list-key {
|
.device-desktop .phui-property-list-key {
|
||||||
width: 18%;
|
width: 12%;
|
||||||
margin-left: 1%;
|
margin-left: 1%;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -51,6 +51,10 @@
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.device-desktop .phui-property-list-has-actions .phui-property-list-key {
|
||||||
|
width: 18%;
|
||||||
|
}
|
||||||
|
|
||||||
.phui-property-list-properties-wrap.phui-property-list-stacked {
|
.phui-property-list-properties-wrap.phui-property-list-stacked {
|
||||||
width: auto;
|
width: auto;
|
||||||
float: none;
|
float: none;
|
||||||
|
@ -71,12 +75,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-desktop .phui-property-list-value {
|
.device-desktop .phui-property-list-value {
|
||||||
width: 78%;
|
width: 84%;
|
||||||
margin-left: 1%;
|
margin-left: 1%;
|
||||||
float: left;
|
float: left;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.device-desktop .phui-property-list-has-actions .phui-property-list-value {
|
||||||
|
width: 78%;
|
||||||
|
}
|
||||||
|
|
||||||
.device .phui-property-list-value,
|
.device .phui-property-list-value,
|
||||||
.phui-property-list-stacked .phui-property-list-properties
|
.phui-property-list-stacked .phui-property-list-properties
|
||||||
.phui-property-list-value {
|
.phui-property-list-value {
|
||||||
|
@ -158,7 +166,7 @@
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phui-property-list-properties-wrap {
|
.phui-property-list-has-actions .phui-property-list-properties-wrap {
|
||||||
float: left;
|
float: left;
|
||||||
width: 78%;
|
width: 78%;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue