mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-22 12:41:19 +01:00
(stable) Promote 2016 Week 18
This commit is contained in:
commit
04db64ca42
71 changed files with 4304 additions and 790 deletions
|
@ -7,8 +7,8 @@
|
||||||
*/
|
*/
|
||||||
return array(
|
return array(
|
||||||
'names' => array(
|
'names' => array(
|
||||||
'core.pkg.css' => '04a95108',
|
'core.pkg.css' => 'b729f9f5',
|
||||||
'core.pkg.js' => '37344f3c',
|
'core.pkg.js' => '6972d365',
|
||||||
'darkconsole.pkg.js' => 'e7393ebb',
|
'darkconsole.pkg.js' => 'e7393ebb',
|
||||||
'differential.pkg.css' => '7ba78475',
|
'differential.pkg.css' => '7ba78475',
|
||||||
'differential.pkg.js' => 'd0cd0df6',
|
'differential.pkg.js' => 'd0cd0df6',
|
||||||
|
@ -136,7 +136,7 @@ return array(
|
||||||
'rsrc/css/phui/phui-form-view.css' => '6a51768e',
|
'rsrc/css/phui/phui-form-view.css' => '6a51768e',
|
||||||
'rsrc/css/phui/phui-form.css' => 'aac1d51d',
|
'rsrc/css/phui/phui-form.css' => 'aac1d51d',
|
||||||
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
||||||
'rsrc/css/phui/phui-header-view.css' => '230254d3',
|
'rsrc/css/phui/phui-header-view.css' => '4c7dd8f5',
|
||||||
'rsrc/css/phui/phui-hovercard.css' => 'de1a2119',
|
'rsrc/css/phui/phui-hovercard.css' => 'de1a2119',
|
||||||
'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
|
'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
|
||||||
'rsrc/css/phui/phui-icon.css' => '3f33ab57',
|
'rsrc/css/phui/phui-icon.css' => '3f33ab57',
|
||||||
|
@ -232,7 +232,7 @@ return array(
|
||||||
'rsrc/externals/javelin/lib/DOM.js' => '805b806a',
|
'rsrc/externals/javelin/lib/DOM.js' => '805b806a',
|
||||||
'rsrc/externals/javelin/lib/History.js' => 'd4505101',
|
'rsrc/externals/javelin/lib/History.js' => 'd4505101',
|
||||||
'rsrc/externals/javelin/lib/JSON.js' => '69adf288',
|
'rsrc/externals/javelin/lib/JSON.js' => '69adf288',
|
||||||
'rsrc/externals/javelin/lib/Leader.js' => 'b4ba945c',
|
'rsrc/externals/javelin/lib/Leader.js' => 'fea0eb47',
|
||||||
'rsrc/externals/javelin/lib/Mask.js' => '8a41885b',
|
'rsrc/externals/javelin/lib/Mask.js' => '8a41885b',
|
||||||
'rsrc/externals/javelin/lib/Quicksand.js' => '6b8ef10b',
|
'rsrc/externals/javelin/lib/Quicksand.js' => '6b8ef10b',
|
||||||
'rsrc/externals/javelin/lib/Request.js' => '94b750d2',
|
'rsrc/externals/javelin/lib/Request.js' => '94b750d2',
|
||||||
|
@ -704,7 +704,7 @@ return array(
|
||||||
'javelin-history' => 'd4505101',
|
'javelin-history' => 'd4505101',
|
||||||
'javelin-install' => '05270951',
|
'javelin-install' => '05270951',
|
||||||
'javelin-json' => '69adf288',
|
'javelin-json' => '69adf288',
|
||||||
'javelin-leader' => 'b4ba945c',
|
'javelin-leader' => 'fea0eb47',
|
||||||
'javelin-magical-init' => '3010e992',
|
'javelin-magical-init' => '3010e992',
|
||||||
'javelin-mask' => '8a41885b',
|
'javelin-mask' => '8a41885b',
|
||||||
'javelin-quicksand' => '6b8ef10b',
|
'javelin-quicksand' => '6b8ef10b',
|
||||||
|
@ -829,7 +829,7 @@ return array(
|
||||||
'phui-form-css' => 'aac1d51d',
|
'phui-form-css' => 'aac1d51d',
|
||||||
'phui-form-view-css' => '6a51768e',
|
'phui-form-view-css' => '6a51768e',
|
||||||
'phui-head-thing-view-css' => 'fd311e5f',
|
'phui-head-thing-view-css' => 'fd311e5f',
|
||||||
'phui-header-view-css' => '230254d3',
|
'phui-header-view-css' => '4c7dd8f5',
|
||||||
'phui-hovercard' => '1bd28176',
|
'phui-hovercard' => '1bd28176',
|
||||||
'phui-hovercard-view-css' => 'de1a2119',
|
'phui-hovercard-view-css' => 'de1a2119',
|
||||||
'phui-icon-set-selector-css' => '1ab67aad',
|
'phui-icon-set-selector-css' => '1ab67aad',
|
||||||
|
@ -1772,9 +1772,6 @@ return array(
|
||||||
'javelin-typeahead-preloaded-source',
|
'javelin-typeahead-preloaded-source',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
'b4ba945c' => array(
|
|
||||||
'javelin-install',
|
|
||||||
),
|
|
||||||
'b59e1e96' => array(
|
'b59e1e96' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
@ -2176,6 +2173,9 @@ return array(
|
||||||
'javelin-view-visitor',
|
'javelin-view-visitor',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
|
'fea0eb47' => array(
|
||||||
|
'javelin-install',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
'packages' => array(
|
'packages' => array(
|
||||||
'core.pkg.css' => array(
|
'core.pkg.css' => array(
|
||||||
|
|
2
resources/sql/autopatches/20160424.locks.1.sql
Normal file
2
resources/sql/autopatches/20160424.locks.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_repository.repository_workingcopyversion
|
||||||
|
ADD lockOwner VARCHAR(255) COLLATE {$COLLATE_TEXT};
|
16
resources/sql/autopatches/20160426.searchedge.sql
Normal file
16
resources/sql/autopatches/20160426.searchedge.sql
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_search.edge (
|
||||||
|
src VARBINARY(64) NOT NULL,
|
||||||
|
type INT UNSIGNED NOT NULL,
|
||||||
|
dst VARBINARY(64) NOT NULL,
|
||||||
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
|
seq INT UNSIGNED NOT NULL,
|
||||||
|
dataID INT UNSIGNED,
|
||||||
|
PRIMARY KEY (src, type, dst),
|
||||||
|
KEY `src` (src, type, dateCreated, seq),
|
||||||
|
UNIQUE KEY `key_dst` (dst, type, src)
|
||||||
|
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
||||||
|
|
||||||
|
CREATE TABLE {$NAMESPACE}_search.edgedata (
|
||||||
|
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
|
||||||
|
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
19
resources/sql/autopatches/20160428.repo.1.urixaction.sql
Normal file
19
resources/sql/autopatches/20160428.repo.1.urixaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_repository.repository_uritransaction (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
phid VARBINARY(64) NOT NULL,
|
||||||
|
authorPHID VARBINARY(64) NOT NULL,
|
||||||
|
objectPHID VARBINARY(64) NOT NULL,
|
||||||
|
viewPolicy VARBINARY(64) NOT NULL,
|
||||||
|
editPolicy VARBINARY(64) NOT NULL,
|
||||||
|
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||||
|
commentVersion INT UNSIGNED NOT NULL,
|
||||||
|
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||||
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
|
dateModified INT UNSIGNED NOT NULL,
|
||||||
|
UNIQUE KEY `key_phid` (`phid`),
|
||||||
|
KEY `key_object` (`objectPHID`)
|
||||||
|
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -10,7 +10,7 @@ AllowUsers vcs-user
|
||||||
# You may need to tweak these options, but mostly they just turn off everything
|
# You may need to tweak these options, but mostly they just turn off everything
|
||||||
# dangerous.
|
# dangerous.
|
||||||
|
|
||||||
Port 22
|
Port 2222
|
||||||
Protocol 2
|
Protocol 2
|
||||||
PermitRootLogin no
|
PermitRootLogin no
|
||||||
AllowAgentForwarding no
|
AllowAgentForwarding no
|
||||||
|
|
|
@ -743,9 +743,13 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php',
|
'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php',
|
||||||
'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php',
|
'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php',
|
||||||
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
|
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
|
||||||
|
'DiffusionRepositoryActionsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php',
|
||||||
|
'DiffusionRepositoryAutomationManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php',
|
||||||
'DiffusionRepositoryBasicsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php',
|
'DiffusionRepositoryBasicsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php',
|
||||||
|
'DiffusionRepositoryBranchesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php',
|
||||||
'DiffusionRepositoryByIDRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryByIDRemarkupRule.php',
|
'DiffusionRepositoryByIDRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryByIDRemarkupRule.php',
|
||||||
'DiffusionRepositoryClusterManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php',
|
'DiffusionRepositoryClusterEngine' => 'applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php',
|
||||||
|
'DiffusionRepositoryClusterEngineLogInterface' => 'applications/diffusion/protocol/DiffusionRepositoryClusterEngineLogInterface.php',
|
||||||
'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
|
'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
|
||||||
'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php',
|
'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php',
|
||||||
'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php',
|
'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php',
|
||||||
|
@ -778,12 +782,19 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php',
|
'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php',
|
||||||
'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
|
'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
|
||||||
'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php',
|
'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php',
|
||||||
|
'DiffusionRepositoryStagingManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php',
|
||||||
'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php',
|
'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php',
|
||||||
|
'DiffusionRepositoryStorageManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php',
|
||||||
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
|
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
|
||||||
|
'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php',
|
||||||
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
||||||
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
||||||
|
'DiffusionRepositoryURIDisableController' => 'applications/diffusion/controller/DiffusionRepositoryURIDisableController.php',
|
||||||
|
'DiffusionRepositoryURIEditController' => 'applications/diffusion/controller/DiffusionRepositoryURIEditController.php',
|
||||||
|
'DiffusionRepositoryURIViewController' => 'applications/diffusion/controller/DiffusionRepositoryURIViewController.php',
|
||||||
'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
|
'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
|
||||||
'DiffusionRepositoryURIsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php',
|
'DiffusionRepositoryURIsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php',
|
||||||
|
'DiffusionRepositoryURIsSearchEngineAttachment' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsSearchEngineAttachment.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',
|
||||||
|
@ -807,6 +818,9 @@ phutil_register_library_map(array(
|
||||||
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
|
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
|
||||||
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
|
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
|
||||||
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
|
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
|
||||||
|
'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php',
|
||||||
|
'DiffusionURIEditEngine' => 'applications/diffusion/editor/DiffusionURIEditEngine.php',
|
||||||
|
'DiffusionURIEditor' => 'applications/diffusion/editor/DiffusionURIEditor.php',
|
||||||
'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php',
|
'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php',
|
||||||
'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php',
|
'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php',
|
||||||
'DiffusionView' => 'applications/diffusion/view/DiffusionView.php',
|
'DiffusionView' => 'applications/diffusion/view/DiffusionView.php',
|
||||||
|
@ -1930,6 +1944,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
|
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
|
||||||
'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
|
'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
|
||||||
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
|
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
|
||||||
|
'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php',
|
||||||
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
||||||
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
|
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
|
||||||
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
||||||
|
@ -3171,6 +3186,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php',
|
'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php',
|
||||||
'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php',
|
'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php',
|
||||||
'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php',
|
'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php',
|
||||||
|
'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php',
|
||||||
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php',
|
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php',
|
||||||
'PhabricatorRepositoryManagementEditWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php',
|
'PhabricatorRepositoryManagementEditWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php',
|
||||||
'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
|
'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
|
||||||
|
@ -3228,7 +3244,11 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
|
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
|
||||||
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
|
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
|
||||||
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
|
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
|
||||||
|
'PhabricatorRepositoryURIPHIDType' => 'applications/repository/phid/PhabricatorRepositoryURIPHIDType.php',
|
||||||
|
'PhabricatorRepositoryURIQuery' => 'applications/repository/query/PhabricatorRepositoryURIQuery.php',
|
||||||
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
|
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
|
||||||
|
'PhabricatorRepositoryURITransaction' => 'applications/repository/storage/PhabricatorRepositoryURITransaction.php',
|
||||||
|
'PhabricatorRepositoryURITransactionQuery' => 'applications/repository/query/PhabricatorRepositoryURITransactionQuery.php',
|
||||||
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
|
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
|
||||||
'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php',
|
'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php',
|
||||||
'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php',
|
'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php',
|
||||||
|
@ -3302,6 +3322,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
|
'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
|
||||||
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
|
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
|
||||||
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
|
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
|
||||||
|
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
|
||||||
'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
|
'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
|
||||||
'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
|
'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
|
||||||
'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
|
'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
|
||||||
|
@ -4420,7 +4441,7 @@ phutil_register_library_map(array(
|
||||||
'ConduitAPIRequest' => 'Phobject',
|
'ConduitAPIRequest' => 'Phobject',
|
||||||
'ConduitAPIResponse' => 'Phobject',
|
'ConduitAPIResponse' => 'Phobject',
|
||||||
'ConduitApplicationNotInstalledException' => 'ConduitMethodNotFoundException',
|
'ConduitApplicationNotInstalledException' => 'ConduitMethodNotFoundException',
|
||||||
'ConduitBoolParameterType' => 'ConduitListParameterType',
|
'ConduitBoolParameterType' => 'ConduitParameterType',
|
||||||
'ConduitCall' => 'Phobject',
|
'ConduitCall' => 'Phobject',
|
||||||
'ConduitCallTestCase' => 'PhabricatorTestCase',
|
'ConduitCallTestCase' => 'PhabricatorTestCase',
|
||||||
'ConduitColumnsParameterType' => 'ConduitParameterType',
|
'ConduitColumnsParameterType' => 'ConduitParameterType',
|
||||||
|
@ -4853,7 +4874,10 @@ phutil_register_library_map(array(
|
||||||
'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
|
'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
|
||||||
'DiffusionGitRequest' => 'DiffusionRequest',
|
'DiffusionGitRequest' => 'DiffusionRequest',
|
||||||
'DiffusionGitResponse' => 'AphrontResponse',
|
'DiffusionGitResponse' => 'AphrontResponse',
|
||||||
'DiffusionGitSSHWorkflow' => 'DiffusionSSHWorkflow',
|
'DiffusionGitSSHWorkflow' => array(
|
||||||
|
'DiffusionSSHWorkflow',
|
||||||
|
'DiffusionRepositoryClusterEngineLogInterface',
|
||||||
|
),
|
||||||
'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
|
'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
|
||||||
'DiffusionHistoryController' => 'DiffusionController',
|
'DiffusionHistoryController' => 'DiffusionController',
|
||||||
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||||
|
@ -4951,9 +4975,12 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRefTableController' => 'DiffusionController',
|
'DiffusionRefTableController' => 'DiffusionController',
|
||||||
'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||||
'DiffusionRenameHistoryQuery' => 'Phobject',
|
'DiffusionRenameHistoryQuery' => 'Phobject',
|
||||||
|
'DiffusionRepositoryActionsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
|
'DiffusionRepositoryAutomationManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
'DiffusionRepositoryBasicsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
'DiffusionRepositoryBasicsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
|
'DiffusionRepositoryBranchesManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||||
'DiffusionRepositoryClusterManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
'DiffusionRepositoryClusterEngine' => 'Phobject',
|
||||||
'DiffusionRepositoryController' => 'DiffusionController',
|
'DiffusionRepositoryController' => 'DiffusionController',
|
||||||
'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController',
|
'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController',
|
||||||
'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource',
|
'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
@ -4986,12 +5013,19 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRepositoryRef' => 'Phobject',
|
'DiffusionRepositoryRef' => 'Phobject',
|
||||||
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||||
'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||||
|
'DiffusionRepositoryStagingManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
|
'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
|
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
|
||||||
|
'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
'DiffusionRepositoryTag' => 'Phobject',
|
'DiffusionRepositoryTag' => 'Phobject',
|
||||||
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
||||||
|
'DiffusionRepositoryURIDisableController' => 'DiffusionController',
|
||||||
|
'DiffusionRepositoryURIEditController' => 'DiffusionController',
|
||||||
|
'DiffusionRepositoryURIViewController' => 'DiffusionController',
|
||||||
'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
||||||
'DiffusionRepositoryURIsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
'DiffusionRepositoryURIsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
|
'DiffusionRepositoryURIsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||||
'DiffusionRequest' => 'Phobject',
|
'DiffusionRequest' => 'Phobject',
|
||||||
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||||
'DiffusionResolveUserQuery' => 'Phobject',
|
'DiffusionResolveUserQuery' => 'Phobject',
|
||||||
|
@ -5015,6 +5049,9 @@ phutil_register_library_map(array(
|
||||||
'DiffusionTagListController' => 'DiffusionController',
|
'DiffusionTagListController' => 'DiffusionController',
|
||||||
'DiffusionTagListView' => 'DiffusionView',
|
'DiffusionTagListView' => 'DiffusionView',
|
||||||
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||||
|
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||||
|
'DiffusionURIEditEngine' => 'PhabricatorEditEngine',
|
||||||
|
'DiffusionURIEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'DiffusionURITestCase' => 'PhutilTestCase',
|
'DiffusionURITestCase' => 'PhutilTestCase',
|
||||||
'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod',
|
'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod',
|
||||||
'DiffusionView' => 'AphrontView',
|
'DiffusionView' => 'AphrontView',
|
||||||
|
@ -6351,6 +6388,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorBoardLayoutEngine' => 'Phobject',
|
'PhabricatorBoardLayoutEngine' => 'Phobject',
|
||||||
'PhabricatorBoardRenderingEngine' => 'Phobject',
|
'PhabricatorBoardRenderingEngine' => 'Phobject',
|
||||||
'PhabricatorBoardResponseEngine' => 'Phobject',
|
'PhabricatorBoardResponseEngine' => 'Phobject',
|
||||||
|
'PhabricatorBoolEditField' => 'PhabricatorEditField',
|
||||||
'PhabricatorBot' => 'PhabricatorDaemon',
|
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||||
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
|
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
|
||||||
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||||
|
@ -7824,6 +7862,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorRepositoryGraphCache' => 'Phobject',
|
'PhabricatorRepositoryGraphCache' => 'Phobject',
|
||||||
'PhabricatorRepositoryGraphStream' => 'Phobject',
|
'PhabricatorRepositoryGraphStream' => 'Phobject',
|
||||||
'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||||
|
'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||||
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||||
'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||||
'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||||
|
@ -7892,11 +7931,21 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
|
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorRepositoryType' => 'Phobject',
|
'PhabricatorRepositoryType' => 'Phobject',
|
||||||
'PhabricatorRepositoryURI' => 'PhabricatorRepositoryDAO',
|
'PhabricatorRepositoryURI' => array(
|
||||||
|
'PhabricatorRepositoryDAO',
|
||||||
|
'PhabricatorApplicationTransactionInterface',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
'PhabricatorExtendedPolicyInterface',
|
||||||
|
'PhabricatorConduitResultInterface',
|
||||||
|
),
|
||||||
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
|
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
|
||||||
'PhabricatorRepositoryURINormalizer' => 'Phobject',
|
'PhabricatorRepositoryURINormalizer' => 'Phobject',
|
||||||
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
|
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType',
|
||||||
|
'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
|
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhabricatorRepositoryURITransaction' => 'PhabricatorApplicationTransaction',
|
||||||
|
'PhabricatorRepositoryURITransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
|
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
|
||||||
'PhabricatorRepositoryVersion' => 'Phobject',
|
'PhabricatorRepositoryVersion' => 'Phobject',
|
||||||
'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO',
|
'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO',
|
||||||
|
@ -7972,6 +8021,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||||
'PhabricatorSearchRelationship' => 'Phobject',
|
'PhabricatorSearchRelationship' => 'Phobject',
|
||||||
'PhabricatorSearchResultView' => 'AphrontView',
|
'PhabricatorSearchResultView' => 'AphrontView',
|
||||||
|
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||||
'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
|
'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
|
||||||
'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
|
'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
|
||||||
'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
|
'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
|
||||||
|
|
|
@ -19,13 +19,6 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
'param' => 'key',
|
'param' => 'key',
|
||||||
'help' => pht('Path to a private key for the host.'),
|
'help' => pht('Path to a private key for the host.'),
|
||||||
),
|
),
|
||||||
array(
|
|
||||||
'name' => 'allow-key-reuse',
|
|
||||||
'help' => pht(
|
|
||||||
'Register even if another host is already registered with this '.
|
|
||||||
'keypair. This is an advanced featuer which allows a pool of '.
|
|
||||||
'devices to share credentials.'),
|
|
||||||
),
|
|
||||||
array(
|
array(
|
||||||
'name' => 'identify-as',
|
'name' => 'identify-as',
|
||||||
'param' => 'name',
|
'param' => 'name',
|
||||||
|
@ -36,13 +29,13 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
array(
|
array(
|
||||||
'name' => 'force',
|
'name' => 'force',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Register this host even if keys already exist.'),
|
'Register this host even if keys already exist on disk.'),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(PhutilArgumentParser $args) {
|
public function execute(PhutilArgumentParser $args) {
|
||||||
$console = PhutilConsole::getConsole();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$device_name = $args->getArg('device');
|
$device_name = $args->getArg('device');
|
||||||
if (!strlen($device_name)) {
|
if (!strlen($device_name)) {
|
||||||
|
@ -51,7 +44,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
$device = id(new AlmanacDeviceQuery())
|
$device = id(new AlmanacDeviceQuery())
|
||||||
->setViewer($this->getViewer())
|
->setViewer($viewer)
|
||||||
->withNames(array($device_name))
|
->withNames(array($device_name))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$device) {
|
if (!$device) {
|
||||||
|
@ -59,6 +52,23 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
pht('No such device "%s" exists!', $device_name));
|
pht('No such device "%s" exists!', $device_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$identify_as = $args->getArg('identify-as');
|
||||||
|
|
||||||
|
$raw_device = $device_name;
|
||||||
|
if (strlen($identify_as)) {
|
||||||
|
$raw_device = $identify_as;
|
||||||
|
}
|
||||||
|
|
||||||
|
$identity_device = id(new AlmanacDeviceQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withNames(array($raw_device))
|
||||||
|
->executeOne();
|
||||||
|
if (!$identity_device) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'No such device "%s" exists!', $raw_device));
|
||||||
|
}
|
||||||
|
|
||||||
$private_key_path = $args->getArg('private-key');
|
$private_key_path = $args->getArg('private-key');
|
||||||
if (!strlen($private_key_path)) {
|
if (!strlen($private_key_path)) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
|
@ -67,7 +77,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
|
|
||||||
if (!Filesystem::pathExists($private_key_path)) {
|
if (!Filesystem::pathExists($private_key_path)) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht('Private key "%s" does not exist!', $private_key_path));
|
pht('No private key exists at path "%s"!', $private_key_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_private_key = Filesystem::readFile($private_key_path);
|
$raw_private_key = Filesystem::readFile($private_key_path);
|
||||||
|
@ -85,8 +95,8 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
if ($err) {
|
if ($err) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'Unable to change ownership of a file to daemon user "%s". Run '.
|
'Unable to change ownership of an identity file to daemon user '.
|
||||||
'this command as %s or root.',
|
'"%s". Run this command as %s or root.',
|
||||||
$phd_user,
|
$phd_user,
|
||||||
$phd_user));
|
$phd_user));
|
||||||
}
|
}
|
||||||
|
@ -133,43 +143,39 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
->withKeys(array($key_object))
|
->withKeys(array($key_object))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if ($public_key) {
|
if (!$public_key) {
|
||||||
if ($public_key->getObjectPHID() !== $device->getPHID()) {
|
throw new PhutilArgumentUsageException(
|
||||||
throw new PhutilArgumentUsageException(
|
pht(
|
||||||
pht(
|
'The public key corresponding to the given private key is not '.
|
||||||
'The public key corresponding to the given private key is '.
|
'yet known to Phabricator. Associate the public key with an '.
|
||||||
'already associated with an object other than the specified '.
|
'Almanac device in the web interface before registering hosts '.
|
||||||
'device. You can not use a single private key to identify '.
|
'with it.'));
|
||||||
'multiple devices or users.'));
|
|
||||||
} else if (!$public_key->getIsTrusted()) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'The public key corresponding to the given private key is '.
|
|
||||||
'already associated with the device, but is not trusted. '.
|
|
||||||
'Registering this key would trust the other entities which '.
|
|
||||||
'hold it. Use a unique key, or explicitly enable trust for the '.
|
|
||||||
'current key.'));
|
|
||||||
} else if (!$args->getArg('allow-key-reuse')) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'The public key corresponding to the given private key is '.
|
|
||||||
'already associated with the device. If you do not want to '.
|
|
||||||
'use a unique key, use --allow-key-reuse to permit '.
|
|
||||||
'reassociation.'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$public_key = id(new PhabricatorAuthSSHKey())
|
|
||||||
->setObjectPHID($device->getPHID())
|
|
||||||
->attachObject($device)
|
|
||||||
->setName($device->getSSHKeyDefaultName())
|
|
||||||
->setKeyType($key_object->getType())
|
|
||||||
->setKeyBody($key_object->getBody())
|
|
||||||
->setKeyComment(pht('Registered'))
|
|
||||||
->setIsTrusted(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($public_key->getObjectPHID() !== $device->getPHID()) {
|
||||||
|
$public_phid = $public_key->getObjectPHID();
|
||||||
|
$public_handles = $viewer->loadHandles(array($public_phid));
|
||||||
|
$public_handle = $public_handles[$public_phid];
|
||||||
|
|
||||||
$console->writeOut(
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'The public key corresponding to the given private key is already '.
|
||||||
|
'associated with an object ("%s") other than the specified '.
|
||||||
|
'device ("%s"). You can not use a single private key to identify '.
|
||||||
|
'multiple devices or users.',
|
||||||
|
$public_handle->getFullName(),
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$public_key->getIsTrusted()) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'The public key corresponding to the given private key is '.
|
||||||
|
'properly associated with the device, but is not yet trusted. '.
|
||||||
|
'Trust this key before registering devices with it.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht('Installing public key...'));
|
pht('Installing public key...'));
|
||||||
|
|
||||||
|
@ -179,18 +185,12 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
Filesystem::writeFile($tmp_public, $raw_public_key);
|
Filesystem::writeFile($tmp_public, $raw_public_key);
|
||||||
execx('mv -f %s %s', $tmp_public, $stored_public_path);
|
execx('mv -f %s %s', $tmp_public, $stored_public_path);
|
||||||
|
|
||||||
$console->writeOut(
|
echo tsprintf(
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht('Installing private key...'));
|
pht('Installing private key...'));
|
||||||
execx('mv -f %s %s', $tmp_private, $stored_private_path);
|
execx('mv -f %s %s', $tmp_private, $stored_private_path);
|
||||||
|
|
||||||
$raw_device = $device_name;
|
echo tsprintf(
|
||||||
$identify_as = $args->getArg('identify-as');
|
|
||||||
if (strlen($identify_as)) {
|
|
||||||
$raw_device = $identify_as;
|
|
||||||
}
|
|
||||||
|
|
||||||
$console->writeOut(
|
|
||||||
"%s\n",
|
"%s\n",
|
||||||
pht('Installing device %s...', $raw_device));
|
pht('Installing device %s...', $raw_device));
|
||||||
|
|
||||||
|
@ -202,14 +202,7 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
Filesystem::writeFile($tmp_device, $raw_device);
|
Filesystem::writeFile($tmp_device, $raw_device);
|
||||||
execx('mv -f %s %s', $tmp_device, $stored_device_path);
|
execx('mv -f %s %s', $tmp_device, $stored_device_path);
|
||||||
|
|
||||||
if (!$public_key->getID()) {
|
echo tsprintf(
|
||||||
$console->writeOut(
|
|
||||||
"%s\n",
|
|
||||||
pht('Registering device key...'));
|
|
||||||
$public_key->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$console->writeOut(
|
|
||||||
"**<bg:green> %s </bg>** %s\n",
|
"**<bg:green> %s </bg>** %s\n",
|
||||||
pht('HOST REGISTERED'),
|
pht('HOST REGISTERED'),
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -274,7 +274,7 @@ abstract class PhabricatorAphlictManagementWorkflow
|
||||||
|
|
||||||
$pid_path = $this->getPIDPath();
|
$pid_path = $this->getPIDPath();
|
||||||
try {
|
try {
|
||||||
$dir = dirname($path);
|
$dir = dirname($pid_path);
|
||||||
if (!Filesystem::pathExists($dir)) {
|
if (!Filesystem::pathExists($dir)) {
|
||||||
Filesystem::createDirectory($dir, 0755, true);
|
Filesystem::createDirectory($dir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ abstract class CelerityResourceController extends PhabricatorController {
|
||||||
// is not, refuse to cache this resource. This avoids poisoning caches
|
// is not, refuse to cache this resource. This avoids poisoning caches
|
||||||
// and CDNs if we're getting a request for a new resource to an old node
|
// and CDNs if we're getting a request for a new resource to an old node
|
||||||
// shortly after a push.
|
// shortly after a push.
|
||||||
$is_cacheable = ($hash === $expect_hash) &&
|
$is_cacheable = ($hash === $expect_hash);
|
||||||
$this->isCacheableResourceType($type);
|
$is_locally_cacheable = $this->isLocallyCacheableResourceType($type);
|
||||||
if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) {
|
if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) {
|
||||||
// Return a "304 Not Modified". We don't care about the value of this
|
// Return a "304 Not Modified". We don't care about the value of this
|
||||||
// field since we never change what resource is served by a given URI.
|
// field since we never change what resource is served by a given URI.
|
||||||
|
@ -60,7 +60,7 @@ abstract class CelerityResourceController extends PhabricatorController {
|
||||||
|
|
||||||
$cache = null;
|
$cache = null;
|
||||||
$data = null;
|
$data = null;
|
||||||
if ($is_cacheable && !$dev_mode) {
|
if ($is_cacheable && $is_locally_cacheable && !$dev_mode) {
|
||||||
$cache = PhabricatorCaches::getImmutableCache();
|
$cache = PhabricatorCaches::getImmutableCache();
|
||||||
|
|
||||||
$request_path = $this->getRequest()->getPath();
|
$request_path = $this->getRequest()->getPath();
|
||||||
|
@ -168,7 +168,7 @@ abstract class CelerityResourceController extends PhabricatorController {
|
||||||
* @param string Resource type.
|
* @param string Resource type.
|
||||||
* @return bool True to enable caching.
|
* @return bool True to enable caching.
|
||||||
*/
|
*/
|
||||||
private function isCacheableResourceType($type) {
|
private function isLocallyCacheableResourceType($type) {
|
||||||
$types = array(
|
$types = array(
|
||||||
'js' => true,
|
'js' => true,
|
||||||
'css' => true,
|
'css' => true,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class ConduitBoolParameterType
|
final class ConduitBoolParameterType
|
||||||
extends ConduitListParameterType {
|
extends ConduitParameterType {
|
||||||
|
|
||||||
protected function getParameterValue(array $request, $key) {
|
protected function getParameterValue(array $request, $key) {
|
||||||
$value = parent::getParameterValue($request, $key);
|
$value = parent::getParameterValue($request, $key);
|
||||||
|
|
|
@ -50,9 +50,35 @@ abstract class PhabricatorSetupCheck extends Phobject {
|
||||||
return $cache->getKey('phabricator.setup.issue-keys');
|
return $cache->getKey('phabricator.setup.issue-keys');
|
||||||
}
|
}
|
||||||
|
|
||||||
final public static function setOpenSetupIssueKeys(array $keys) {
|
final public static function setOpenSetupIssueKeys(
|
||||||
|
array $keys,
|
||||||
|
$update_database) {
|
||||||
$cache = PhabricatorCaches::getSetupCache();
|
$cache = PhabricatorCaches::getSetupCache();
|
||||||
$cache->setKey('phabricator.setup.issue-keys', $keys);
|
$cache->setKey('phabricator.setup.issue-keys', $keys);
|
||||||
|
|
||||||
|
if ($update_database) {
|
||||||
|
$db_cache = new PhabricatorKeyValueDatabaseCache();
|
||||||
|
try {
|
||||||
|
$json = phutil_json_encode($keys);
|
||||||
|
$db_cache->setKey('phabricator.setup.issue-keys', $json);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
// Ignore any write failures, since they likely just indicate that we
|
||||||
|
// have a database-related setup issue that needs to be resolved.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function getOpenSetupIssueKeysFromDatabase() {
|
||||||
|
$db_cache = new PhabricatorKeyValueDatabaseCache();
|
||||||
|
try {
|
||||||
|
$value = $db_cache->getKey('phabricator.setup.issue-keys');
|
||||||
|
if (!strlen($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return phutil_json_decode($value);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final public static function getUnignoredIssueKeys(array $all_issues) {
|
final public static function getUnignoredIssueKeys(array $all_issues) {
|
||||||
|
@ -97,7 +123,21 @@ abstract class PhabricatorSetupCheck extends Phobject {
|
||||||
->setView($view);
|
->setView($view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self::setOpenSetupIssueKeys(self::getUnignoredIssueKeys($issues));
|
$issue_keys = self::getUnignoredIssueKeys($issues);
|
||||||
|
self::setOpenSetupIssueKeys($issue_keys, $update_database = true);
|
||||||
|
} else if ($issue_keys) {
|
||||||
|
// If Phabricator is configured in a cluster with multiple web devices,
|
||||||
|
// we can end up with setup issues cached on every device. This can cause
|
||||||
|
// a warning banner to show on every device so that each one needs to
|
||||||
|
// be dismissed individually, which is pretty annoying. See T10876.
|
||||||
|
|
||||||
|
// To avoid this, check if the issues we found have already been cleared
|
||||||
|
// in the database. If they have, we'll just wipe out our own cache and
|
||||||
|
// move on.
|
||||||
|
$issue_keys = self::getOpenSetupIssueKeysFromDatabase();
|
||||||
|
if ($issue_keys !== null) {
|
||||||
|
self::setOpenSetupIssueKeys($issue_keys, $update_database = false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to repair configuration unless we have a clean bill of health on it.
|
// Try to repair configuration unless we have a clean bill of health on it.
|
||||||
|
|
|
@ -11,7 +11,8 @@ final class PhabricatorConfigIssueListController
|
||||||
|
|
||||||
$issues = PhabricatorSetupCheck::runAllChecks();
|
$issues = PhabricatorSetupCheck::runAllChecks();
|
||||||
PhabricatorSetupCheck::setOpenSetupIssueKeys(
|
PhabricatorSetupCheck::setOpenSetupIssueKeys(
|
||||||
PhabricatorSetupCheck::getUnignoredIssueKeys($issues));
|
PhabricatorSetupCheck::getUnignoredIssueKeys($issues),
|
||||||
|
$update_database = true);
|
||||||
|
|
||||||
$important = $this->buildIssueList(
|
$important = $this->buildIssueList(
|
||||||
$issues, PhabricatorSetupCheck::GROUP_IMPORTANT);
|
$issues, PhabricatorSetupCheck::GROUP_IMPORTANT);
|
||||||
|
|
|
@ -9,7 +9,8 @@ final class PhabricatorConfigIssueViewController
|
||||||
|
|
||||||
$issues = PhabricatorSetupCheck::runAllChecks();
|
$issues = PhabricatorSetupCheck::runAllChecks();
|
||||||
PhabricatorSetupCheck::setOpenSetupIssueKeys(
|
PhabricatorSetupCheck::setOpenSetupIssueKeys(
|
||||||
PhabricatorSetupCheck::getUnignoredIssueKeys($issues));
|
PhabricatorSetupCheck::getUnignoredIssueKeys($issues),
|
||||||
|
$update_database = true);
|
||||||
|
|
||||||
if (empty($issues[$issue_key])) {
|
if (empty($issues[$issue_key])) {
|
||||||
$content = id(new PHUIInfoView())
|
$content = id(new PHUIInfoView())
|
||||||
|
|
|
@ -91,6 +91,13 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
||||||
=> 'DiffusionCommitEditController',
|
=> 'DiffusionCommitEditController',
|
||||||
'manage/(?:(?P<panel>[^/]+)/)?'
|
'manage/(?:(?P<panel>[^/]+)/)?'
|
||||||
=> 'DiffusionRepositoryManageController',
|
=> 'DiffusionRepositoryManageController',
|
||||||
|
'uri/' => array(
|
||||||
|
'view/(?P<id>[0-9]\d*)/' => 'DiffusionRepositoryURIViewController',
|
||||||
|
'disable/(?P<id>[0-9]\d*)/'
|
||||||
|
=> 'DiffusionRepositoryURIDisableController',
|
||||||
|
$this->getEditRoutePattern('edit/')
|
||||||
|
=> 'DiffusionRepositoryURIEditController',
|
||||||
|
),
|
||||||
'edit/' => array(
|
'edit/' => array(
|
||||||
'' => 'DiffusionRepositoryEditMainController',
|
'' => 'DiffusionRepositoryEditMainController',
|
||||||
'basic/' => 'DiffusionRepositoryEditBasicController',
|
'basic/' => 'DiffusionRepositoryEditBasicController',
|
||||||
|
|
|
@ -29,21 +29,25 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
||||||
protected function execute(ConduitAPIRequest $request) {
|
protected function execute(ConduitAPIRequest $request) {
|
||||||
$need_messages = $request->getValue('needMessages');
|
$need_messages = $request->getValue('needMessages');
|
||||||
$bypass_cache = $request->getValue('bypassCache');
|
$bypass_cache = $request->getValue('bypassCache');
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
$query = id(new DiffusionCommitQuery())
|
$query = id(new DiffusionCommitQuery())
|
||||||
->setViewer($request->getUser())
|
->setViewer($viewer)
|
||||||
->needCommitData(true);
|
->needCommitData(true);
|
||||||
|
|
||||||
$repository_phid = $request->getValue('repositoryPHID');
|
$repository_phid = $request->getValue('repositoryPHID');
|
||||||
if ($repository_phid) {
|
if ($repository_phid) {
|
||||||
$repository = id(new PhabricatorRepositoryQuery())
|
$repository = id(new PhabricatorRepositoryQuery())
|
||||||
->setViewer($request->getUser())
|
->setViewer($viewer)
|
||||||
->withPHIDs(array($repository_phid))
|
->withPHIDs(array($repository_phid))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if ($repository) {
|
if ($repository) {
|
||||||
$query->withRepository($repository);
|
$query->withRepository($repository);
|
||||||
if ($bypass_cache) {
|
if ($bypass_cache) {
|
||||||
$repository->synchronizeWorkingCopyBeforeRead();
|
id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setRepository($repository)
|
||||||
|
->synchronizeWorkingCopyBeforeRead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,10 +124,11 @@ abstract class DiffusionQueryConduitAPIMethod
|
||||||
// to prevent infinite recursion.
|
// to prevent infinite recursion.
|
||||||
|
|
||||||
$is_cluster_request = $request->getIsClusterRequest();
|
$is_cluster_request = $request->getIsClusterRequest();
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
$repository = $drequest->getRepository();
|
$repository = $drequest->getRepository();
|
||||||
$client = $repository->newConduitClient(
|
$client = $repository->newConduitClient(
|
||||||
$request->getUser(),
|
$viewer,
|
||||||
$is_cluster_request);
|
$is_cluster_request);
|
||||||
if ($client) {
|
if ($client) {
|
||||||
// We're proxying, so just make an intracluster call.
|
// We're proxying, so just make an intracluster call.
|
||||||
|
@ -149,7 +150,10 @@ abstract class DiffusionQueryConduitAPIMethod
|
||||||
// fetching the most up-to-date data? Synchronization can be slow, and a
|
// fetching the most up-to-date data? Synchronization can be slow, and a
|
||||||
// lot of web reads are probably fine if they're a few seconds out of
|
// lot of web reads are probably fine if they're a few seconds out of
|
||||||
// date.
|
// date.
|
||||||
$repository->synchronizeWorkingCopyBeforeRead();
|
id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setRepository($repository)
|
||||||
|
->synchronizeWorkingCopyBeforeRead();
|
||||||
|
|
||||||
return $this->getResult($request);
|
return $this->getResult($request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionURIEditConduitAPIMethod
|
||||||
|
extends PhabricatorEditEngineAPIMethod {
|
||||||
|
|
||||||
|
public function getAPIMethodName() {
|
||||||
|
return 'diffusion.uri.edit';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newEditEngine() {
|
||||||
|
return new DiffusionURIEditEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethodSummary() {
|
||||||
|
return pht(
|
||||||
|
'Apply transactions to create a new repository URI or edit an existing '.
|
||||||
|
'one.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,9 +4,66 @@ final class DiffusionRepositoryEditproController
|
||||||
extends DiffusionRepositoryEditController {
|
extends DiffusionRepositoryEditController {
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
return id(new DiffusionRepositoryEditEngine())
|
$engine = id(new DiffusionRepositoryEditEngine())
|
||||||
->setController($this)
|
->setController($this);
|
||||||
->buildResponse();
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
if (!$id) {
|
||||||
|
$this->requireApplicationCapability(
|
||||||
|
DiffusionCreateRepositoriesCapability::CAPABILITY);
|
||||||
|
|
||||||
|
$vcs = $request->getStr('vcs');
|
||||||
|
$vcs_types = PhabricatorRepositoryType::getRepositoryTypeMap();
|
||||||
|
if (empty($vcs_types[$vcs])) {
|
||||||
|
return $this->buildVCSTypeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine
|
||||||
|
->addContextParameter('vcs', $vcs)
|
||||||
|
->setVersionControlSystem($vcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $engine->buildResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildVCSTypeResponse() {
|
||||||
|
$vcs_types = PhabricatorRepositoryType::getRepositoryTypeMap();
|
||||||
|
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addTextCrumb(pht('Create Repository'));
|
||||||
|
$crumbs->setBorder(true);
|
||||||
|
|
||||||
|
$title = pht('Choose Repository Type');
|
||||||
|
$header = id(new PHUIHeaderView())
|
||||||
|
->setHeader(pht('Create Repository'))
|
||||||
|
->setHeaderIcon('fa-plus-square');
|
||||||
|
|
||||||
|
$layout = id(new AphrontMultiColumnView())
|
||||||
|
->setFluidLayout(true);
|
||||||
|
|
||||||
|
$create_uri = $request->getRequestURI();
|
||||||
|
|
||||||
|
foreach ($vcs_types as $vcs_key => $vcs_type) {
|
||||||
|
$action = id(new PHUIActionPanelView())
|
||||||
|
->setIcon(idx($vcs_type, 'icon'))
|
||||||
|
->setHeader(idx($vcs_type, 'create.header'))
|
||||||
|
->setHref($create_uri->alter('vcs', $vcs_key))
|
||||||
|
->setSubheader(idx($vcs_type, 'create.subheader'));
|
||||||
|
|
||||||
|
$layout->addColumn($action);
|
||||||
|
}
|
||||||
|
|
||||||
|
$view = id(new PHUITwoColumnView())
|
||||||
|
->setHeader($header)
|
||||||
|
->setFooter($layout);
|
||||||
|
|
||||||
|
return $this->newPage()
|
||||||
|
->setTitle($title)
|
||||||
|
->setCrumbs($crumbs)
|
||||||
|
->appendChild($view);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryURIDisableController
|
||||||
|
extends DiffusionController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$response = $this->loadDiffusionContextForEdit();
|
||||||
|
if ($response) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$drequest = $this->getDiffusionRequest();
|
||||||
|
$repository = $drequest->getRepository();
|
||||||
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
$uri = id(new PhabricatorRepositoryURIQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->withRepositories(array($repository))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$uri) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_disabled = $uri->getIsDisabled();
|
||||||
|
$view_uri = $uri->getViewURI();
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorRepositoryURITransaction())
|
||||||
|
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_DISABLE)
|
||||||
|
->setNewValue(!$is_disabled);
|
||||||
|
|
||||||
|
$editor = id(new DiffusionURIEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->applyTransactions($uri, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_disabled) {
|
||||||
|
$title = pht('Enable URI');
|
||||||
|
$body = pht(
|
||||||
|
'Enable this URI? Any configured behaviors will begin working '.
|
||||||
|
'again.');
|
||||||
|
$button = pht('Enable URI');
|
||||||
|
} else {
|
||||||
|
$title = pht('Disable URI');
|
||||||
|
$body = pht(
|
||||||
|
'Disable this URI? It will no longer be observed, fetched, mirrored, '.
|
||||||
|
'served or shown to users.');
|
||||||
|
$button = pht('Disable URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle($title)
|
||||||
|
->appendParagraph($body)
|
||||||
|
->addCancelButton($view_uri)
|
||||||
|
->addSubmitButton($button);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryURIEditController
|
||||||
|
extends DiffusionController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$response = $this->loadDiffusionContextForEdit();
|
||||||
|
if ($response) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$drequest = $this->getDiffusionRequest();
|
||||||
|
$repository = $drequest->getRepository();
|
||||||
|
|
||||||
|
return id(new DiffusionURIEditEngine())
|
||||||
|
->setController($this)
|
||||||
|
->setRepository($repository)
|
||||||
|
->buildResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryURIViewController
|
||||||
|
extends DiffusionController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$response = $this->loadDiffusionContext();
|
||||||
|
if ($response) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$drequest = $this->getDiffusionRequest();
|
||||||
|
$repository = $drequest->getRepository();
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
|
$uri = id(new PhabricatorRepositoryURIQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->withRepositories(array($repository))
|
||||||
|
->executeOne();
|
||||||
|
if (!$uri) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = array(
|
||||||
|
pht('URI'),
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addTextCrumb(
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$repository->getURI());
|
||||||
|
$crumbs->addTextCrumb(
|
||||||
|
pht('Manage'),
|
||||||
|
$repository->getPathURI('manage/'));
|
||||||
|
|
||||||
|
$panel_label = id(new DiffusionRepositoryURIsManagementPanel())
|
||||||
|
->getManagementPanelLabel();
|
||||||
|
$panel_uri = $repository->getPathURI('manage/uris/');
|
||||||
|
$crumbs->addTextCrumb($panel_label, $panel_uri);
|
||||||
|
|
||||||
|
$crumbs->addTextCrumb(pht('URI %d', $uri->getID()));
|
||||||
|
|
||||||
|
$header_text = pht(
|
||||||
|
'%s: URI %d',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$uri->getID());
|
||||||
|
|
||||||
|
$header = id(new PHUIHeaderView())
|
||||||
|
->setHeader($header_text)
|
||||||
|
->setHeaderIcon('fa-pencil');
|
||||||
|
if ($uri->getIsDisabled()) {
|
||||||
|
$header->setStatus('fa-ban', 'dark', pht('Disabled'));
|
||||||
|
} else {
|
||||||
|
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$curtain = $this->buildCurtain($uri);
|
||||||
|
$details = $this->buildPropertySection($uri);
|
||||||
|
|
||||||
|
$timeline = $this->buildTransactionTimeline(
|
||||||
|
$uri,
|
||||||
|
new PhabricatorRepositoryURITransactionQuery());
|
||||||
|
$timeline->setShouldTerminate(true);
|
||||||
|
|
||||||
|
$view = id(new PHUITwoColumnView())
|
||||||
|
->setHeader($header)
|
||||||
|
->setMainColumn(
|
||||||
|
array(
|
||||||
|
$details,
|
||||||
|
$timeline,
|
||||||
|
))
|
||||||
|
->setCurtain($curtain);
|
||||||
|
|
||||||
|
return $this->newPage()
|
||||||
|
->setTitle($title)
|
||||||
|
->setCrumbs($crumbs)
|
||||||
|
->appendChild($view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCurtain(PhabricatorRepositoryURI $uri) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$id = $uri->getID();
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$uri,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
|
||||||
|
$curtain = $this->newCurtainView($uri);
|
||||||
|
|
||||||
|
$edit_uri = $uri->getEditURI();
|
||||||
|
|
||||||
|
$curtain->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-pencil')
|
||||||
|
->setName(pht('Edit URI'))
|
||||||
|
->setHref($edit_uri)
|
||||||
|
->setWorkflow(!$can_edit)
|
||||||
|
->setDisabled(!$can_edit));
|
||||||
|
|
||||||
|
if ($uri->getIsDisabled()) {
|
||||||
|
$disable_name = pht('Enable URI');
|
||||||
|
$disable_icon = 'fa-check';
|
||||||
|
} else {
|
||||||
|
$disable_name = pht('Disable URI');
|
||||||
|
$disable_icon = 'fa-ban';
|
||||||
|
}
|
||||||
|
|
||||||
|
$disable_uri = $uri->getRepository()->getPathURI("uri/disable/{$id}/");
|
||||||
|
|
||||||
|
$curtain->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon($disable_icon)
|
||||||
|
->setName($disable_name)
|
||||||
|
->setHref($disable_uri)
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_edit));
|
||||||
|
|
||||||
|
return $curtain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPropertySection(PhabricatorRepositoryURI $uri) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$properties = id(new PHUIPropertyListView())
|
||||||
|
->setUser($viewer);
|
||||||
|
|
||||||
|
$properties->addProperty(pht('URI'), $uri->getDisplayURI());
|
||||||
|
$properties->addProperty(pht('Credential'), 'TODO');
|
||||||
|
|
||||||
|
|
||||||
|
$io_type = $uri->getEffectiveIOType();
|
||||||
|
$io_map = PhabricatorRepositoryURI::getIOTypeMap();
|
||||||
|
$io_spec = idx($io_map, $io_type, array());
|
||||||
|
|
||||||
|
$io_icon = idx($io_spec, 'icon');
|
||||||
|
$io_color = idx($io_spec, 'color');
|
||||||
|
$io_label = idx($io_spec, 'label', $io_type);
|
||||||
|
$io_note = idx($io_spec, 'note');
|
||||||
|
|
||||||
|
$io_item = id(new PHUIStatusItemView())
|
||||||
|
->setIcon($io_icon, $io_color)
|
||||||
|
->setTarget(phutil_tag('strong', array(), $io_label))
|
||||||
|
->setNote($io_note);
|
||||||
|
|
||||||
|
$io_view = id(new PHUIStatusListView())
|
||||||
|
->addItem($io_item);
|
||||||
|
|
||||||
|
$properties->addProperty(pht('I/O'), $io_view);
|
||||||
|
|
||||||
|
|
||||||
|
$display_type = $uri->getEffectiveDisplayType();
|
||||||
|
$display_map = PhabricatorRepositoryURI::getDisplayTypeMap();
|
||||||
|
$display_spec = idx($display_map, $display_type, array());
|
||||||
|
|
||||||
|
$display_icon = idx($display_spec, 'icon');
|
||||||
|
$display_color = idx($display_spec, 'color');
|
||||||
|
$display_label = idx($display_spec, 'label', $display_type);
|
||||||
|
$display_note = idx($display_spec, 'note');
|
||||||
|
|
||||||
|
$display_item = id(new PHUIStatusItemView())
|
||||||
|
->setIcon($display_icon, $display_color)
|
||||||
|
->setTarget(phutil_tag('strong', array(), $display_label))
|
||||||
|
->setNote($display_note);
|
||||||
|
|
||||||
|
$display_view = id(new PHUIStatusListView())
|
||||||
|
->addItem($display_item);
|
||||||
|
|
||||||
|
$properties->addProperty(pht('Display'), $display_view);
|
||||||
|
|
||||||
|
|
||||||
|
return id(new PHUIObjectBoxView())
|
||||||
|
->setHeaderText(pht('Details'))
|
||||||
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||||
|
->appendChild($properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -540,12 +540,16 @@ final class DiffusionServeController extends DiffusionController {
|
||||||
|
|
||||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
|
||||||
|
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setRepository($repository);
|
||||||
|
|
||||||
$did_write_lock = false;
|
$did_write_lock = false;
|
||||||
if ($this->isReadOnlyRequest($repository)) {
|
if ($this->isReadOnlyRequest($repository)) {
|
||||||
$repository->synchronizeWorkingCopyBeforeRead();
|
$cluster_engine->synchronizeWorkingCopyBeforeRead();
|
||||||
} else {
|
} else {
|
||||||
$did_write_lock = true;
|
$did_write_lock = true;
|
||||||
$repository->synchronizeWorkingCopyBeforeWrite($viewer);
|
$cluster_engine->synchronizeWorkingCopyBeforeWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
$caught = null;
|
$caught = null;
|
||||||
|
@ -559,7 +563,7 @@ final class DiffusionServeController extends DiffusionController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($did_write_lock) {
|
if ($did_write_lock) {
|
||||||
$repository->synchronizeWorkingCopyAfterWrite();
|
$cluster_engine->synchronizeWorkingCopyAfterWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($unguarded);
|
unset($unguarded);
|
||||||
|
|
|
@ -5,6 +5,17 @@ final class DiffusionRepositoryEditEngine
|
||||||
|
|
||||||
const ENGINECONST = 'diffusion.repository';
|
const ENGINECONST = 'diffusion.repository';
|
||||||
|
|
||||||
|
private $versionControlSystem;
|
||||||
|
|
||||||
|
public function setVersionControlSystem($version_control_system) {
|
||||||
|
$this->versionControlSystem = $version_control_system;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersionControlSystem() {
|
||||||
|
return $this->versionControlSystem;
|
||||||
|
}
|
||||||
|
|
||||||
public function isEngineConfigurable() {
|
public function isEngineConfigurable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +38,14 @@ final class DiffusionRepositoryEditEngine
|
||||||
|
|
||||||
protected function newEditableObject() {
|
protected function newEditableObject() {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
return PhabricatorRepository::initializeNewRepository($viewer);
|
$repository = PhabricatorRepository::initializeNewRepository($viewer);
|
||||||
|
|
||||||
|
$vcs = $this->getVersionControlSystem();
|
||||||
|
if ($vcs) {
|
||||||
|
$repository->setVersionControlSystem($vcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function newObjectQuery() {
|
protected function newObjectQuery() {
|
||||||
|
@ -75,6 +93,12 @@ final class DiffusionRepositoryEditEngine
|
||||||
->setObject($object)
|
->setObject($object)
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
|
$track_value = $object->getDetail('branch-filter', array());
|
||||||
|
$track_value = array_keys($track_value);
|
||||||
|
|
||||||
|
$autoclose_value = $object->getDetail('close-commits-filter', array());
|
||||||
|
$autoclose_value = array_keys($autoclose_value);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
id(new PhabricatorSelectEditField())
|
id(new PhabricatorSelectEditField())
|
||||||
->setKey('vcs')
|
->setKey('vcs')
|
||||||
|
@ -132,6 +156,19 @@ final class DiffusionRepositoryEditEngine
|
||||||
->setConduitDescription(pht('Change the default text encoding.'))
|
->setConduitDescription(pht('Change the default text encoding.'))
|
||||||
->setConduitTypeDescription(pht('New text encoding.'))
|
->setConduitTypeDescription(pht('New text encoding.'))
|
||||||
->setValue($object->getDetail('encoding')),
|
->setValue($object->getDetail('encoding')),
|
||||||
|
id(new PhabricatorBoolEditField())
|
||||||
|
->setKey('allowDangerousChanges')
|
||||||
|
->setLabel(pht('Allow Dangerous Changes'))
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setIsConduitOnly(true)
|
||||||
|
->setOptions(
|
||||||
|
pht('Prevent Dangerous Changes'),
|
||||||
|
pht('Allow Dangerous Changes'))
|
||||||
|
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_DANGEROUS)
|
||||||
|
->setDescription(pht('Permit dangerous changes to be made.'))
|
||||||
|
->setConduitDescription(pht('Allow or prevent dangerous changes.'))
|
||||||
|
->setConduitTypeDescription(pht('New protection setting.'))
|
||||||
|
->setValue($object->shouldAllowDangerousChanges()),
|
||||||
id(new PhabricatorSelectEditField())
|
id(new PhabricatorSelectEditField())
|
||||||
->setKey('status')
|
->setKey('status')
|
||||||
->setLabel(pht('Status'))
|
->setLabel(pht('Status'))
|
||||||
|
@ -152,6 +189,99 @@ final class DiffusionRepositoryEditEngine
|
||||||
->setConduitDescription(pht('Set the default branch name.'))
|
->setConduitDescription(pht('Set the default branch name.'))
|
||||||
->setConduitTypeDescription(pht('New default branch name.'))
|
->setConduitTypeDescription(pht('New default branch name.'))
|
||||||
->setValue($object->getDetail('default-branch')),
|
->setValue($object->getDetail('default-branch')),
|
||||||
|
id(new PhabricatorTextAreaEditField())
|
||||||
|
->setIsStringList(true)
|
||||||
|
->setKey('trackOnly')
|
||||||
|
->setLabel(pht('Track Only'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setDescription(pht('Track only these branches.'))
|
||||||
|
->setConduitDescription(pht('Set the tracked branches.'))
|
||||||
|
->setConduitTypeDescription(pht('New tracked branchs.'))
|
||||||
|
->setValue($track_value),
|
||||||
|
id(new PhabricatorTextAreaEditField())
|
||||||
|
->setIsStringList(true)
|
||||||
|
->setKey('autocloseOnly')
|
||||||
|
->setLabel(pht('Autoclose Only'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setDescription(pht('Autoclose commits on only these branches.'))
|
||||||
|
->setConduitDescription(pht('Set the autoclose branches.'))
|
||||||
|
->setConduitTypeDescription(pht('New default tracked branchs.'))
|
||||||
|
->setValue($autoclose_value),
|
||||||
|
id(new PhabricatorTextEditField())
|
||||||
|
->setKey('stagingAreaURI')
|
||||||
|
->setLabel(pht('Staging Area URI'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_STAGING_URI)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setDescription(pht('Staging area URI.'))
|
||||||
|
->setConduitDescription(pht('Set the staging area URI.'))
|
||||||
|
->setConduitTypeDescription(pht('New staging area URI.'))
|
||||||
|
->setValue($object->getStagingURI()),
|
||||||
|
id(new PhabricatorDatasourceEditField())
|
||||||
|
->setKey('automationBlueprintPHIDs')
|
||||||
|
->setLabel(pht('Use Blueprints'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setDatasource(new DrydockBlueprintDatasource())
|
||||||
|
->setDescription(pht('Automation blueprints.'))
|
||||||
|
->setConduitDescription(pht('Change automation blueprints.'))
|
||||||
|
->setConduitTypeDescription(pht('New blueprint PHIDs.'))
|
||||||
|
->setValue($object->getAutomationBlueprintPHIDs()),
|
||||||
|
id(new PhabricatorStringListEditField())
|
||||||
|
->setKey('symbolLanguages')
|
||||||
|
->setLabel(pht('Languages'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setDescription(
|
||||||
|
pht('Languages which define symbols in this repository.'))
|
||||||
|
->setConduitDescription(
|
||||||
|
pht('Change symbol languages for this repository.'))
|
||||||
|
->setConduitTypeDescription(
|
||||||
|
pht('New symbol langauges.'))
|
||||||
|
->setValue($object->getSymbolLanguages()),
|
||||||
|
id(new PhabricatorDatasourceEditField())
|
||||||
|
->setKey('symbolRepositoryPHIDs')
|
||||||
|
->setLabel(pht('Uses Symbols From'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setDatasource(new DiffusionRepositoryDatasource())
|
||||||
|
->setDescription(pht('Repositories to link symbols from.'))
|
||||||
|
->setConduitDescription(pht('Change symbol source repositories.'))
|
||||||
|
->setConduitTypeDescription(pht('New symbol repositories.'))
|
||||||
|
->setValue($object->getSymbolSources()),
|
||||||
|
id(new PhabricatorBoolEditField())
|
||||||
|
->setKey('publish')
|
||||||
|
->setLabel(pht('Publish/Notify'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_NOTIFY)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setOptions(
|
||||||
|
pht('Disable Notifications, Feed, and Herald'),
|
||||||
|
pht('Enable Notifications, Feed, and Herald'))
|
||||||
|
->setDescription(pht('Configure how changes are published.'))
|
||||||
|
->setConduitDescription(pht('Change publishing options.'))
|
||||||
|
->setConduitTypeDescription(pht('New notification setting.'))
|
||||||
|
->setValue(!$object->getDetail('herald-disabled')),
|
||||||
|
id(new PhabricatorBoolEditField())
|
||||||
|
->setKey('autoclose')
|
||||||
|
->setLabel(pht('Autoclose'))
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE)
|
||||||
|
->setIsCopyable(true)
|
||||||
|
->setOptions(
|
||||||
|
pht('Disable Autoclose'),
|
||||||
|
pht('Enable Autoclose'))
|
||||||
|
->setDescription(pht('Stop or resume autoclosing in this repository.'))
|
||||||
|
->setConduitDescription(pht('Change autoclose setting.'))
|
||||||
|
->setConduitTypeDescription(pht('New autoclose setting.'))
|
||||||
|
->setValue(!$object->getDetail('disable-autoclose')),
|
||||||
id(new PhabricatorPolicyEditField())
|
id(new PhabricatorPolicyEditField())
|
||||||
->setKey('policy.push')
|
->setKey('policy.push')
|
||||||
->setLabel(pht('Push Policy'))
|
->setLabel(pht('Push Policy'))
|
||||||
|
|
156
src/applications/diffusion/editor/DiffusionURIEditEngine.php
Normal file
156
src/applications/diffusion/editor/DiffusionURIEditEngine.php
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionURIEditEngine
|
||||||
|
extends PhabricatorEditEngine {
|
||||||
|
|
||||||
|
const ENGINECONST = 'diffusion.uri';
|
||||||
|
|
||||||
|
private $repository;
|
||||||
|
|
||||||
|
public function setRepository(PhabricatorRepository $repository) {
|
||||||
|
$this->repository = $repository;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRepository() {
|
||||||
|
return $this->repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEngineConfigurable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEngineName() {
|
||||||
|
return pht('Repository URIs');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSummaryHeader() {
|
||||||
|
return pht('Edit Repository URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSummaryText() {
|
||||||
|
return pht('Creates and edits repository URIs.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEngineApplicationClass() {
|
||||||
|
return 'PhabricatorDiffusionApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newEditableObject() {
|
||||||
|
$uri = PhabricatorRepositoryURI::initializeNewURI();
|
||||||
|
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
if ($repository) {
|
||||||
|
$uri->setRepositoryPHID($repository->getPHID());
|
||||||
|
$uri->attachRepository($repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newObjectQuery() {
|
||||||
|
return new PhabricatorRepositoryURIQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectCreateTitleText($object) {
|
||||||
|
return pht('Create Repository URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectCreateButtonText($object) {
|
||||||
|
return pht('Create Repository URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectEditTitleText($object) {
|
||||||
|
return pht('Edit Repository URI %d', $object->getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectEditShortText($object) {
|
||||||
|
return pht('URI %d', $object->getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectCreateShortText() {
|
||||||
|
return pht('Create Repository URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectName() {
|
||||||
|
return pht('Repository URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectViewURI($object) {
|
||||||
|
return $object->getViewURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildCustomEditFields($object) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorHandlesEditField())
|
||||||
|
->setKey('repository')
|
||||||
|
->setAliases(array('repositoryPHID'))
|
||||||
|
->setLabel(pht('Repository'))
|
||||||
|
->setIsRequired(true)
|
||||||
|
->setIsConduitOnly(true)
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryURITransaction::TYPE_REPOSITORY)
|
||||||
|
->setDescription(pht('The repository this URI is associated with.'))
|
||||||
|
->setConduitDescription(
|
||||||
|
pht(
|
||||||
|
'Create a URI in a given repository. This transaction type '.
|
||||||
|
'must be present when creating a new URI and must not be '.
|
||||||
|
'present when editing an existing URI.'))
|
||||||
|
->setConduitTypeDescription(
|
||||||
|
pht('Repository PHID to create a new URI for.'))
|
||||||
|
->setSingleValue($object->getRepositoryPHID()),
|
||||||
|
id(new PhabricatorTextEditField())
|
||||||
|
->setKey('uri')
|
||||||
|
->setLabel(pht('URI'))
|
||||||
|
->setIsRequired(true)
|
||||||
|
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_URI)
|
||||||
|
->setDescription(pht('The repository URI.'))
|
||||||
|
->setConduitDescription(pht('Change the repository URI.'))
|
||||||
|
->setConduitTypeDescription(pht('New repository URI.'))
|
||||||
|
->setValue($object->getURI()),
|
||||||
|
id(new PhabricatorSelectEditField())
|
||||||
|
->setKey('io')
|
||||||
|
->setLabel(pht('I/O Type'))
|
||||||
|
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_IO)
|
||||||
|
->setDescription(pht('URI I/O behavior.'))
|
||||||
|
->setConduitDescription(pht('Adjust I/O behavior.'))
|
||||||
|
->setConduitTypeDescription(pht('New I/O behavior.'))
|
||||||
|
->setValue($object->getIOType())
|
||||||
|
->setOptions($object->getAvailableIOTypeOptions()),
|
||||||
|
id(new PhabricatorSelectEditField())
|
||||||
|
->setKey('display')
|
||||||
|
->setLabel(pht('Display Type'))
|
||||||
|
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_DISPLAY)
|
||||||
|
->setDescription(pht('URI display behavior.'))
|
||||||
|
->setConduitDescription(pht('Change display behavior.'))
|
||||||
|
->setConduitTypeDescription(pht('New display behavior.'))
|
||||||
|
->setValue($object->getDisplayType())
|
||||||
|
->setOptions($object->getAvailableDisplayTypeOptions()),
|
||||||
|
id(new PhabricatorHandlesEditField())
|
||||||
|
->setKey('credential')
|
||||||
|
->setAliases(array('credentialPHID'))
|
||||||
|
->setLabel(pht('Credential'))
|
||||||
|
->setIsConduitOnly(true)
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL)
|
||||||
|
->setDescription(
|
||||||
|
pht('The credential to use when interacting with this URI.'))
|
||||||
|
->setConduitDescription(pht('Change the credential for this URI.'))
|
||||||
|
->setConduitTypeDescription(pht('New credential PHID, or null.'))
|
||||||
|
->setSingleValue($object->getCredentialPHID()),
|
||||||
|
id(new PhabricatorBoolEditField())
|
||||||
|
->setKey('disable')
|
||||||
|
->setLabel(pht('Disabled'))
|
||||||
|
->setIsConduitOnly(true)
|
||||||
|
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_DISABLE)
|
||||||
|
->setDescription(pht('Active status of the URI.'))
|
||||||
|
->setConduitDescription(pht('Disable or activate the URI.'))
|
||||||
|
->setConduitTypeDescription(pht('True to disable the URI.'))
|
||||||
|
->setOptions(pht('Enable'), pht('Disable'))
|
||||||
|
->setValue($object->getIsDisabled()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
373
src/applications/diffusion/editor/DiffusionURIEditor.php
Normal file
373
src/applications/diffusion/editor/DiffusionURIEditor.php
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionURIEditor
|
||||||
|
extends PhabricatorApplicationTransactionEditor {
|
||||||
|
|
||||||
|
public function getEditorApplicationClass() {
|
||||||
|
return 'PhabricatorDiffusionApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditorObjectsDescription() {
|
||||||
|
return pht('Diffusion URIs');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransactionTypes() {
|
||||||
|
$types = parent::getTransactionTypes();
|
||||||
|
|
||||||
|
$types[] = PhabricatorRepositoryURITransaction::TYPE_REPOSITORY;
|
||||||
|
$types[] = PhabricatorRepositoryURITransaction::TYPE_URI;
|
||||||
|
$types[] = PhabricatorRepositoryURITransaction::TYPE_IO;
|
||||||
|
$types[] = PhabricatorRepositoryURITransaction::TYPE_DISPLAY;
|
||||||
|
$types[] = PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL;
|
||||||
|
$types[] = PhabricatorRepositoryURITransaction::TYPE_DISABLE;
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCustomTransactionOldValue(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
||||||
|
return $object->getURI();
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
||||||
|
return $object->getIOType();
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
|
||||||
|
return $object->getDisplayType();
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
|
||||||
|
return $object->getRepositoryPHID();
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
|
||||||
|
return $object->getCredentialPHID();
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
|
||||||
|
return (int)$object->getIsDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCustomTransactionNewValue(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
|
||||||
|
return $xaction->getNewValue();
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
|
||||||
|
return (int)$xaction->getNewValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyCustomInternalTransaction(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
||||||
|
$object->setURI($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
||||||
|
$object->setIOType($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
|
||||||
|
$object->setDisplayType($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
|
||||||
|
$object->setRepositoryPHID($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
|
||||||
|
$object->setCredentialPHID($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
|
||||||
|
$object->setIsDisabled($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyCustomExternalTransaction(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateTransaction(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
$type,
|
||||||
|
array $xactions) {
|
||||||
|
|
||||||
|
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
|
||||||
|
$missing = $this->validateIsEmptyTextField(
|
||||||
|
$object->getRepositoryPHID(),
|
||||||
|
$xactions);
|
||||||
|
if ($missing) {
|
||||||
|
// NOTE: This isn't being marked as a missing field error because
|
||||||
|
// it's a fundamental, required property of the URI.
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Required'),
|
||||||
|
pht(
|
||||||
|
'When creating a repository URI, you must specify which '.
|
||||||
|
'repository the URI will belong to.'),
|
||||||
|
nonempty(last($xactions), null));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewer = $this->getActor();
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$repository_phid = $xaction->getNewValue();
|
||||||
|
|
||||||
|
// If this isn't changing anything, let it through as-is.
|
||||||
|
if ($repository_phid == $object->getRepositoryPHID()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->getIsNewObject()) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht(
|
||||||
|
'The repository a URI is associated with is immutable, and '.
|
||||||
|
'can not be changed after the URI is created.'),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repository = id(new PhabricatorRepositoryQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($repository_phid))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$repository) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht(
|
||||||
|
'To create a URI for a repository ("%s"), it must exist and '.
|
||||||
|
'you must have permission to edit it.',
|
||||||
|
$repository_phid),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
|
||||||
|
$viewer = $this->getActor();
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$credential_phid = $xaction->getNewValue();
|
||||||
|
|
||||||
|
if ($credential_phid == $object->getCredentialPHID()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$credential = id(new PassphraseCredentialQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($credential_phid))
|
||||||
|
->executeOne();
|
||||||
|
if (!$credential) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht(
|
||||||
|
'You can only associate a credential ("%s") with a repository '.
|
||||||
|
'URI if it exists and you have permission to see it.',
|
||||||
|
$credential_phid),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
||||||
|
$missing = $this->validateIsEmptyTextField(
|
||||||
|
$object->getURI(),
|
||||||
|
$xactions);
|
||||||
|
|
||||||
|
if ($missing) {
|
||||||
|
$error = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Required'),
|
||||||
|
pht('A repository URI must have a nonempty URI.'),
|
||||||
|
nonempty(last($xactions), null));
|
||||||
|
|
||||||
|
$error->setIsMissingFieldError(true);
|
||||||
|
$errors[] = $error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$new_uri = $xaction->getNewValue();
|
||||||
|
if ($new_uri == $object->getURI()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PhabricatorRepository::assertValidRemoteURI($new_uri);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
$ex->getMessage(),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
||||||
|
$available = $object->getAvailableIOTypeOptions();
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$new = $xaction->getNewValue();
|
||||||
|
|
||||||
|
if (empty($available[$new])) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht(
|
||||||
|
'Value "%s" is not a valid display setting for this URI. '.
|
||||||
|
'Available types for this URI are: %s.',
|
||||||
|
implode(', ', array_keys($available))),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are setting this URI to use "Observe", we must have no
|
||||||
|
// other "Observe" URIs and must also have no "Read/Write" URIs.
|
||||||
|
|
||||||
|
// If we are setting this URI to "Read/Write", we must have no
|
||||||
|
// other "Observe" URIs. It's OK to have other "Read/Write" URIs.
|
||||||
|
|
||||||
|
$no_observers = false;
|
||||||
|
$no_readwrite = false;
|
||||||
|
switch ($new) {
|
||||||
|
case PhabricatorRepositoryURI::IO_OBSERVE:
|
||||||
|
$no_readwrite = true;
|
||||||
|
$no_observers = true;
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURI::IO_READWRITE:
|
||||||
|
$no_observers = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($no_observers || $no_readwrite) {
|
||||||
|
$repository = id(new PhabricatorRepositoryQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withPHIDs(array($object->getRepositoryPHID()))
|
||||||
|
->needURIs(true)
|
||||||
|
->executeOne();
|
||||||
|
$uris = $repository->getURIs();
|
||||||
|
|
||||||
|
$observe_conflict = null;
|
||||||
|
$readwrite_conflict = null;
|
||||||
|
foreach ($uris as $uri) {
|
||||||
|
// If this is the URI being edited, it can not conflict with
|
||||||
|
// itself.
|
||||||
|
if ($uri->getID() == $object->getID()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io_type = $uri->getIoType();
|
||||||
|
|
||||||
|
if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) {
|
||||||
|
if ($no_readwrite) {
|
||||||
|
$readwite_conflict = $uri;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($io_type == PhabricatorRepositoryURI::IO_OBSERVE) {
|
||||||
|
if ($no_observers) {
|
||||||
|
$observe_conflict = $uri;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($observe_conflict) {
|
||||||
|
if ($new == PhabricatorRepositoryURI::IO_OBSERVE) {
|
||||||
|
$message = pht(
|
||||||
|
'You can not set this URI to use Observe IO because '.
|
||||||
|
'another URI for this repository is already configured '.
|
||||||
|
'in Observe IO mode. A repository can not observe two '.
|
||||||
|
'different remotes simultaneously. Turn off IO for the '.
|
||||||
|
'other URI first.');
|
||||||
|
} else {
|
||||||
|
$message = pht(
|
||||||
|
'You can not set this URI to use Read/Write IO because '.
|
||||||
|
'another URI for this repository is already configured '.
|
||||||
|
'in Observe IO mode. An observed repository can not be '.
|
||||||
|
'made writable. Turn off IO for the other URI first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
$message,
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($readwrite_conflict) {
|
||||||
|
$message = pht(
|
||||||
|
'You can not set this URI to use Observe IO because '.
|
||||||
|
'another URI for this repository is already configured '.
|
||||||
|
'in Read/Write IO mode. A repository can not simultaneously '.
|
||||||
|
'be writable and observe a remote. Turn off IO for the '.
|
||||||
|
'other URI first.');
|
||||||
|
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
$message,
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
|
||||||
|
$available = $object->getAvailableDisplayTypeOptions();
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$new = $xaction->getNewValue();
|
||||||
|
|
||||||
|
if (empty($available[$new])) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht(
|
||||||
|
'Value "%s" is not a valid display setting for this URI. '.
|
||||||
|
'Available types for this URI are: %s.',
|
||||||
|
implode(', ', array_keys($available))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryURIsSearchEngineAttachment
|
||||||
|
extends PhabricatorSearchEngineAttachment {
|
||||||
|
|
||||||
|
public function getAttachmentName() {
|
||||||
|
return pht('Repository URIs');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttachmentDescription() {
|
||||||
|
return pht('Get a list of associated URIs for each repository.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function willLoadAttachmentData($query, $spec) {
|
||||||
|
$query->needURIs(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttachmentForObject($object, $data, $spec) {
|
||||||
|
$uris = array();
|
||||||
|
foreach ($object->getURIs() as $uri) {
|
||||||
|
$uris[] = array(
|
||||||
|
'id' => $uri->getID(),
|
||||||
|
'type' => phid_get_type($uri->getPHID()),
|
||||||
|
'phid' => $uri->getPHID(),
|
||||||
|
'fields' => $uri->getFieldValuesForConduit(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'uris' => $uris,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryActionsManagementPanel
|
||||||
|
extends DiffusionRepositoryManagementPanel {
|
||||||
|
|
||||||
|
const PANELKEY = 'actions';
|
||||||
|
|
||||||
|
public function getManagementPanelLabel() {
|
||||||
|
return pht('Actions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManagementPanelOrder() {
|
||||||
|
return 1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildManagementPanelActions() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$repository,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$actions_uri = $repository->getPathURI('edit/actions/');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-pencil')
|
||||||
|
->setName(pht('Edit Actions'))
|
||||||
|
->setHref($actions_uri)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildManagementPanelContent() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setActionList($this->newActions());
|
||||||
|
|
||||||
|
$notify = $repository->getDetail('herald-disabled')
|
||||||
|
? pht('Off')
|
||||||
|
: pht('On');
|
||||||
|
$notify = phutil_tag('em', array(), $notify);
|
||||||
|
$view->addProperty(pht('Publish/Notify'), $notify);
|
||||||
|
|
||||||
|
$autoclose = $repository->getDetail('disable-autoclose')
|
||||||
|
? pht('Off')
|
||||||
|
: pht('On');
|
||||||
|
$autoclose = phutil_tag('em', array(), $autoclose);
|
||||||
|
$view->addProperty(pht('Autoclose'), $autoclose);
|
||||||
|
|
||||||
|
return $this->newBox(pht('Branches'), $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryAutomationManagementPanel
|
||||||
|
extends DiffusionRepositoryManagementPanel {
|
||||||
|
|
||||||
|
const PANELKEY = 'automation';
|
||||||
|
|
||||||
|
public function getManagementPanelLabel() {
|
||||||
|
return pht('Automation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManagementPanelOrder() {
|
||||||
|
return 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildManagementPanelActions() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$repository,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$can_test = $can_edit && $repository->canPerformAutomation();
|
||||||
|
|
||||||
|
$automation_uri = $repository->getPathURI('edit/automation/');
|
||||||
|
$test_uri = $repository->getPathURI('edit/testautomation/');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-pencil')
|
||||||
|
->setName(pht('Edit Automation'))
|
||||||
|
->setHref($automation_uri)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit),
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-gamepad')
|
||||||
|
->setName(pht('Test Configuration'))
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_test)
|
||||||
|
->setHref($test_uri),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildManagementPanelContent() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setActionList($this->newActions());
|
||||||
|
|
||||||
|
$blueprint_phids = $repository->getAutomationBlueprintPHIDs();
|
||||||
|
if (!$blueprint_phids) {
|
||||||
|
$blueprint_view = phutil_tag('em', array(), pht('Not Configured'));
|
||||||
|
} else {
|
||||||
|
$blueprint_view = id(new DrydockObjectAuthorizationView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setObjectPHID($repository->getPHID())
|
||||||
|
->setBlueprintPHIDs($blueprint_phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->addProperty(pht('Automation'), $blueprint_view);
|
||||||
|
|
||||||
|
return $this->newBox(pht('Automation'), $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ final class DiffusionRepositoryBasicsManagementPanel
|
||||||
$activate_uri = $repository->getPathURI('edit/activate/');
|
$activate_uri = $repository->getPathURI('edit/activate/');
|
||||||
$delete_uri = $repository->getPathURI('edit/delete/');
|
$delete_uri = $repository->getPathURI('edit/delete/');
|
||||||
$encoding_uri = $repository->getPathURI('edit/encoding/');
|
$encoding_uri = $repository->getPathURI('edit/encoding/');
|
||||||
|
$dangerous_uri = $repository->getPathURI('edit/dangerous/');
|
||||||
|
|
||||||
if ($repository->isTracked()) {
|
if ($repository->isTracked()) {
|
||||||
$activate_icon = 'fa-pause';
|
$activate_icon = 'fa-pause';
|
||||||
|
@ -35,6 +36,17 @@ final class DiffusionRepositoryBasicsManagementPanel
|
||||||
$activate_label = pht('Activate Repository');
|
$activate_label = pht('Activate Repository');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$should_dangerous = $repository->shouldAllowDangerousChanges();
|
||||||
|
if ($should_dangerous) {
|
||||||
|
$dangerous_icon = 'fa-shield';
|
||||||
|
$dangerous_name = pht('Prevent Dangerous Changes');
|
||||||
|
$can_dangerous = $can_edit;
|
||||||
|
} else {
|
||||||
|
$dangerous_icon = 'fa-bullseye';
|
||||||
|
$dangerous_name = pht('Allow Dangerous Changes');
|
||||||
|
$can_dangerous = ($can_edit && $repository->canAllowDangerousChanges());
|
||||||
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
->setIcon('fa-pencil')
|
->setIcon('fa-pencil')
|
||||||
|
@ -48,6 +60,12 @@ final class DiffusionRepositoryBasicsManagementPanel
|
||||||
->setHref($encoding_uri)
|
->setHref($encoding_uri)
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(!$can_edit),
|
->setWorkflow(!$can_edit),
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon($dangerous_icon)
|
||||||
|
->setName($dangerous_name)
|
||||||
|
->setHref($dangerous_uri)
|
||||||
|
->setDisabled(!$can_dangerous)
|
||||||
|
->setWorkflow(true),
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
->setHref($activate_uri)
|
->setHref($activate_uri)
|
||||||
->setIcon($activate_icon)
|
->setIcon($activate_icon)
|
||||||
|
@ -110,6 +128,20 @@ final class DiffusionRepositoryBasicsManagementPanel
|
||||||
}
|
}
|
||||||
$view->addProperty(pht('Encoding'), $encoding);
|
$view->addProperty(pht('Encoding'), $encoding);
|
||||||
|
|
||||||
|
$can_dangerous = $repository->canAllowDangerousChanges();
|
||||||
|
if (!$can_dangerous) {
|
||||||
|
$dangerous = phutil_tag('em', array(), pht('Not Preventable'));
|
||||||
|
} else {
|
||||||
|
$should_dangerous = $repository->shouldAllowDangerousChanges();
|
||||||
|
if ($should_dangerous) {
|
||||||
|
$dangerous = pht('Allowed');
|
||||||
|
} else {
|
||||||
|
$dangerous = pht('Not Allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->addProperty(pht('Dangerous Changes'), $dangerous);
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryBranchesManagementPanel
|
||||||
|
extends DiffusionRepositoryManagementPanel {
|
||||||
|
|
||||||
|
const PANELKEY = 'branches';
|
||||||
|
|
||||||
|
public function getManagementPanelLabel() {
|
||||||
|
return pht('Branches');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManagementPanelOrder() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildManagementPanelActions() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$repository,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$branches_uri = $repository->getPathURI('edit/branches/');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-pencil')
|
||||||
|
->setName(pht('Edit Branches'))
|
||||||
|
->setHref($branches_uri)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildManagementPanelContent() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setActionList($this->newActions());
|
||||||
|
|
||||||
|
$default_branch = nonempty(
|
||||||
|
$repository->getHumanReadableDetail('default-branch'),
|
||||||
|
phutil_tag('em', array(), $repository->getDefaultBranch()));
|
||||||
|
$view->addProperty(pht('Default Branch'), $default_branch);
|
||||||
|
|
||||||
|
$track_only = nonempty(
|
||||||
|
$repository->getHumanReadableDetail('branch-filter', array()),
|
||||||
|
phutil_tag('em', array(), pht('Track All Branches')));
|
||||||
|
$view->addProperty(pht('Track Only'), $track_only);
|
||||||
|
|
||||||
|
$autoclose_only = nonempty(
|
||||||
|
$repository->getHumanReadableDetail('close-commits-filter', array()),
|
||||||
|
phutil_tag('em', array(), pht('Autoclose On All Branches')));
|
||||||
|
|
||||||
|
if ($repository->getDetail('disable-autoclose')) {
|
||||||
|
$autoclose_only = phutil_tag('em', array(), pht('Disabled'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->addProperty(pht('Autoclose Only'), $autoclose_only);
|
||||||
|
|
||||||
|
return $this->newBox(pht('Branches'), $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ final class DiffusionRepositoryHistoryManagementPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getManagementPanelOrder() {
|
public function getManagementPanelOrder() {
|
||||||
return 900;
|
return 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildManagementPanelContent() {
|
public function buildManagementPanelContent() {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryStagingManagementPanel
|
||||||
|
extends DiffusionRepositoryManagementPanel {
|
||||||
|
|
||||||
|
const PANELKEY = 'staging';
|
||||||
|
|
||||||
|
public function getManagementPanelLabel() {
|
||||||
|
return pht('Staging Area');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManagementPanelOrder() {
|
||||||
|
return 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildManagementPanelActions() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$repository,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$staging_uri = $repository->getPathURI('edit/staging/');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-pencil')
|
||||||
|
->setName(pht('Edit Staging'))
|
||||||
|
->setHref($staging_uri)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildManagementPanelContent() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setActionList($this->newActions());
|
||||||
|
|
||||||
|
$staging_uri = $repository->getStagingURI();
|
||||||
|
if (!$staging_uri) {
|
||||||
|
$staging_uri = phutil_tag('em', array(), pht('No Staging Area'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->addProperty(pht('Staging Area URI'), $staging_uri);
|
||||||
|
|
||||||
|
return $this->newBox(pht('Staging Area'), $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -46,8 +46,12 @@ final class DiffusionRepositoryStatusManagementPanel
|
||||||
pht('Update Frequency'),
|
pht('Update Frequency'),
|
||||||
$this->buildRepositoryUpdateInterval($repository));
|
$this->buildRepositoryUpdateInterval($repository));
|
||||||
|
|
||||||
|
$messages = id(new PhabricatorRepositoryStatusMessage())
|
||||||
|
->loadAllWhere('repositoryID = %d', $repository->getID());
|
||||||
|
$messages = mpull($messages, null, 'getStatusType');
|
||||||
|
|
||||||
list($status, $raw_error) = $this->buildRepositoryStatus($repository);
|
$status = $this->buildRepositoryStatus($repository, $messages);
|
||||||
|
$raw_error = $this->buildRepositoryRawError($repository, $messages);
|
||||||
|
|
||||||
$view->addProperty(pht('Status'), $status);
|
$view->addProperty(pht('Status'), $status);
|
||||||
if ($raw_error) {
|
if ($raw_error) {
|
||||||
|
@ -80,17 +84,14 @@ final class DiffusionRepositoryStatusManagementPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildRepositoryStatus(
|
private function buildRepositoryStatus(
|
||||||
PhabricatorRepository $repository) {
|
PhabricatorRepository $repository,
|
||||||
|
array $messages) {
|
||||||
|
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
$is_cluster = $repository->getAlmanacServicePHID();
|
$is_cluster = $repository->getAlmanacServicePHID();
|
||||||
|
|
||||||
$view = new PHUIStatusListView();
|
$view = new PHUIStatusListView();
|
||||||
|
|
||||||
$messages = id(new PhabricatorRepositoryStatusMessage())
|
|
||||||
->loadAllWhere('repositoryID = %d', $repository->getID());
|
|
||||||
$messages = mpull($messages, null, 'getStatusType');
|
|
||||||
|
|
||||||
if ($repository->isTracked()) {
|
if ($repository->isTracked()) {
|
||||||
$view->addItem(
|
$view->addItem(
|
||||||
id(new PHUIStatusItemView())
|
id(new PHUIStatusItemView())
|
||||||
|
@ -361,8 +362,6 @@ final class DiffusionRepositoryStatusManagementPanel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_error = null;
|
|
||||||
|
|
||||||
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
|
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
|
||||||
if ($message) {
|
if ($message) {
|
||||||
switch ($message->getStatusCode()) {
|
switch ($message->getStatusCode()) {
|
||||||
|
@ -377,8 +376,6 @@ final class DiffusionRepositoryStatusManagementPanel
|
||||||
'access the repository.');
|
'access the repository.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_error = $message;
|
|
||||||
|
|
||||||
$view->addItem(
|
$view->addItem(
|
||||||
id(new PHUIStatusItemView())
|
id(new PHUIStatusItemView())
|
||||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||||
|
@ -432,11 +429,30 @@ final class DiffusionRepositoryStatusManagementPanel
|
||||||
->setNote(pht('This repository will be updated soon!')));
|
->setNote(pht('This repository will be updated soon!')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRepositoryRawError(
|
||||||
|
PhabricatorRepository $repository,
|
||||||
|
array $messages) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
$viewer,
|
$viewer,
|
||||||
$repository,
|
$repository,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$raw_error = null;
|
||||||
|
|
||||||
|
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
|
||||||
|
if ($message) {
|
||||||
|
switch ($message->getStatusCode()) {
|
||||||
|
case PhabricatorRepositoryStatusMessage::CODE_ERROR:
|
||||||
|
$raw_error = $message->getParameter('message');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($raw_error !== null) {
|
if ($raw_error !== null) {
|
||||||
if (!$can_edit) {
|
if (!$can_edit) {
|
||||||
$raw_message = pht(
|
$raw_message = pht(
|
||||||
|
@ -450,7 +466,7 @@ final class DiffusionRepositoryStatusManagementPanel
|
||||||
$raw_message = null;
|
$raw_message = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($view, $raw_message);
|
return $raw_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class DiffusionRepositoryClusterManagementPanel
|
final class DiffusionRepositoryStorageManagementPanel
|
||||||
extends DiffusionRepositoryManagementPanel {
|
extends DiffusionRepositoryManagementPanel {
|
||||||
|
|
||||||
const PANELKEY = 'cluster';
|
const PANELKEY = 'storage';
|
||||||
|
|
||||||
public function getManagementPanelLabel() {
|
public function getManagementPanelLabel() {
|
||||||
return pht('Cluster Configuration');
|
return pht('Storage');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getManagementPanelOrder() {
|
public function getManagementPanelOrder() {
|
||||||
|
@ -14,6 +14,45 @@ final class DiffusionRepositoryClusterManagementPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildManagementPanelContent() {
|
public function buildManagementPanelContent() {
|
||||||
|
return array(
|
||||||
|
$this->buildStorageStatusPanel(),
|
||||||
|
$this->buildClusterStatusPanel(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildStorageStatusPanel() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setViewer($viewer);
|
||||||
|
|
||||||
|
if ($repository->usesLocalWorkingCopy()) {
|
||||||
|
$storage_path = $repository->getHumanReadableDetail('local-path');
|
||||||
|
} else {
|
||||||
|
$storage_path = phutil_tag('em', array(), pht('No Local Working Copy'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$service_phid = $repository->getAlmanacServicePHID();
|
||||||
|
if ($service_phid) {
|
||||||
|
$storage_service = $viewer->renderHandle($service_phid);
|
||||||
|
} else {
|
||||||
|
$storage_service = phutil_tag('em', array(), pht('Local'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->addProperty(pht('Storage Path'), $storage_path);
|
||||||
|
$view->addProperty(pht('Storage Cluster'), $storage_service);
|
||||||
|
|
||||||
|
$header = id(new PHUIHeaderView())
|
||||||
|
->setHeader(pht('Storage'));
|
||||||
|
|
||||||
|
return id(new PHUIObjectBoxView())
|
||||||
|
->setHeader($header)
|
||||||
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||||
|
->addPropertyList($view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildClusterStatusPanel() {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
@ -175,18 +214,6 @@ final class DiffusionRepositoryClusterManagementPanel
|
||||||
->setTag('a')
|
->setTag('a')
|
||||||
->setText(pht('Documentation')));
|
->setText(pht('Documentation')));
|
||||||
|
|
||||||
if ($service) {
|
|
||||||
$header->setSubheader(
|
|
||||||
pht(
|
|
||||||
'This repository is hosted on %s.',
|
|
||||||
phutil_tag(
|
|
||||||
'a',
|
|
||||||
array(
|
|
||||||
'href' => $service->getURI(),
|
|
||||||
),
|
|
||||||
$service->getName())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return id(new PHUIObjectBoxView())
|
return id(new PHUIObjectBoxView())
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositorySymbolsManagementPanel
|
||||||
|
extends DiffusionRepositoryManagementPanel {
|
||||||
|
|
||||||
|
const PANELKEY = 'symbols';
|
||||||
|
|
||||||
|
public function getManagementPanelLabel() {
|
||||||
|
return pht('Symbols');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getManagementPanelOrder() {
|
||||||
|
return 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildManagementPanelActions() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$repository,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$symbols_uri = $repository->getPathURI('edit/symbols/');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-pencil')
|
||||||
|
->setName(pht('Edit Symbols'))
|
||||||
|
->setHref($symbols_uri)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildManagementPanelContent() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$view = id(new PHUIPropertyListView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setActionList($this->newActions());
|
||||||
|
|
||||||
|
$languages = $repository->getSymbolLanguages();
|
||||||
|
if ($languages) {
|
||||||
|
$languages = implode(', ', $languages);
|
||||||
|
} else {
|
||||||
|
$languages = phutil_tag('em', array(), pht('Any'));
|
||||||
|
}
|
||||||
|
$view->addProperty(pht('Languages'), $languages);
|
||||||
|
|
||||||
|
$sources = $repository->getSymbolSources();
|
||||||
|
if ($sources) {
|
||||||
|
$sources = $viewer->renderHandleList($sources);
|
||||||
|
} else {
|
||||||
|
$sources = phutil_tag('em', array(), pht('This Repository Only'));
|
||||||
|
}
|
||||||
|
$view->addProperty(pht('Uses Symbols From'), $sources);
|
||||||
|
|
||||||
|
return $this->newBox(pht('Symbols'), $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,8 +16,6 @@ final class DiffusionRepositoryURIsManagementPanel
|
||||||
public function buildManagementPanelContent() {
|
public function buildManagementPanelContent() {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$repository->attachURIs(array());
|
|
||||||
$uris = $repository->getURIs();
|
$uris = $repository->getURIs();
|
||||||
|
|
||||||
Javelin::initBehavior('phabricator-tooltips');
|
Javelin::initBehavior('phabricator-tooltips');
|
||||||
|
@ -25,6 +23,12 @@ final class DiffusionRepositoryURIsManagementPanel
|
||||||
foreach ($uris as $uri) {
|
foreach ($uris as $uri) {
|
||||||
|
|
||||||
$uri_name = $uri->getDisplayURI();
|
$uri_name = $uri->getDisplayURI();
|
||||||
|
$uri_name = phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $uri->getViewURI(),
|
||||||
|
),
|
||||||
|
$uri_name);
|
||||||
|
|
||||||
if ($uri->getIsDisabled()) {
|
if ($uri->getIsDisabled()) {
|
||||||
$status_icon = 'fa-times grey';
|
$status_icon = 'fa-times grey';
|
||||||
|
@ -34,48 +38,30 @@ final class DiffusionRepositoryURIsManagementPanel
|
||||||
|
|
||||||
$uri_status = id(new PHUIIconView())->setIcon($status_icon);
|
$uri_status = id(new PHUIIconView())->setIcon($status_icon);
|
||||||
|
|
||||||
switch ($uri->getEffectiveIOType()) {
|
$io_type = $uri->getEffectiveIOType();
|
||||||
case PhabricatorRepositoryURI::IO_OBSERVE:
|
$io_map = PhabricatorRepositoryURI::getIOTypeMap();
|
||||||
$io_icon = 'fa-download green';
|
$io_spec = idx($io_map, $io_type, array());
|
||||||
$io_label = pht('Observe');
|
|
||||||
break;
|
$io_icon = idx($io_spec, 'icon');
|
||||||
case PhabricatorRepositoryURI::IO_MIRROR:
|
$io_color = idx($io_spec, 'color');
|
||||||
$io_icon = 'fa-upload green';
|
$io_label = idx($io_spec, 'label', $io_type);
|
||||||
$io_label = pht('Mirror');
|
|
||||||
break;
|
|
||||||
case PhabricatorRepositoryURI::IO_NONE:
|
|
||||||
$io_icon = 'fa-times grey';
|
|
||||||
$io_label = pht('No I/O');
|
|
||||||
break;
|
|
||||||
case PhabricatorRepositoryURI::IO_READ:
|
|
||||||
$io_icon = 'fa-folder blue';
|
|
||||||
$io_label = pht('Read Only');
|
|
||||||
break;
|
|
||||||
case PhabricatorRepositoryURI::IO_READWRITE:
|
|
||||||
$io_icon = 'fa-folder-open blue';
|
|
||||||
$io_label = pht('Read/Write');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$uri_io = array(
|
$uri_io = array(
|
||||||
id(new PHUIIconView())->setIcon($io_icon),
|
id(new PHUIIconView())->setIcon("{$io_icon} {$io_color}"),
|
||||||
' ',
|
' ',
|
||||||
$io_label,
|
$io_label,
|
||||||
);
|
);
|
||||||
|
|
||||||
switch ($uri->getEffectiveDisplayType()) {
|
$display_type = $uri->getEffectiveDisplayType();
|
||||||
case PhabricatorRepositoryURI::DISPLAY_NEVER:
|
$display_map = PhabricatorRepositoryURI::getDisplayTypeMap();
|
||||||
$display_icon = 'fa-eye-slash grey';
|
$display_spec = idx($display_map, $display_type, array());
|
||||||
$display_label = pht('Hidden');
|
|
||||||
break;
|
$display_icon = idx($display_spec, 'icon');
|
||||||
case PhabricatorRepositoryURI::DISPLAY_ALWAYS:
|
$display_color = idx($display_spec, 'color');
|
||||||
$display_icon = 'fa-eye green';
|
$display_label = idx($display_spec, 'label', $display_type);
|
||||||
$display_label = pht('Visible');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$uri_display = array(
|
$uri_display = array(
|
||||||
id(new PHUIIconView())->setIcon($display_icon),
|
id(new PHUIIconView())->setIcon("{$display_icon} {$display_color}"),
|
||||||
' ',
|
' ',
|
||||||
$display_label,
|
$display_label,
|
||||||
);
|
);
|
||||||
|
@ -105,11 +91,17 @@ final class DiffusionRepositoryURIsManagementPanel
|
||||||
null,
|
null,
|
||||||
));
|
));
|
||||||
|
|
||||||
$doc_href = PhabricatorEnv::getDoclink(
|
$doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs');
|
||||||
'Diffusion User Guide: Repository URIs');
|
$add_href = $repository->getPathURI('uri/edit/');
|
||||||
|
|
||||||
$header = id(new PHUIHeaderView())
|
$header = id(new PHUIHeaderView())
|
||||||
->setHeader(pht('Repository URIs'))
|
->setHeader(pht('Repository URIs'))
|
||||||
|
->addActionLink(
|
||||||
|
id(new PHUIButtonView())
|
||||||
|
->setIcon('fa-plus')
|
||||||
|
->setHref($add_href)
|
||||||
|
->setTag('a')
|
||||||
|
->setText(pht('Add New URI')))
|
||||||
->addActionLink(
|
->addActionLink(
|
||||||
id(new PHUIButtonView())
|
id(new PHUIButtonView())
|
||||||
->setIcon('fa-book')
|
->setIcon('fa-book')
|
||||||
|
|
|
@ -0,0 +1,625 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages repository synchronization for cluster repositories.
|
||||||
|
*
|
||||||
|
* @task config Configuring Synchronization
|
||||||
|
* @task sync Cluster Synchronization
|
||||||
|
* @task internal Internals
|
||||||
|
*/
|
||||||
|
final class DiffusionRepositoryClusterEngine extends Phobject {
|
||||||
|
|
||||||
|
private $repository;
|
||||||
|
private $viewer;
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
private $clusterWriteLock;
|
||||||
|
private $clusterWriteVersion;
|
||||||
|
private $clusterWriteOwner;
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Configuring Synchronization )---------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function setRepository(PhabricatorRepository $repository) {
|
||||||
|
$this->repository = $repository;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRepository() {
|
||||||
|
return $this->repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setViewer(PhabricatorUser $viewer) {
|
||||||
|
$this->viewer = $viewer;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewer() {
|
||||||
|
return $this->viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLog(DiffusionRepositoryClusterEngineLogInterface $log) {
|
||||||
|
$this->logger = $log;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Cluster Synchronization )-------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize repository version information after creating a repository.
|
||||||
|
*
|
||||||
|
* This initializes working copy versions for all currently bound devices to
|
||||||
|
* 0, so that we don't get stuck making an ambiguous choice about which
|
||||||
|
* devices are leaders when we later synchronize before a read.
|
||||||
|
*
|
||||||
|
* @task sync
|
||||||
|
*/
|
||||||
|
public function synchronizeWorkingCopyAfterCreation() {
|
||||||
|
if (!$this->shouldEnableSynchronization()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$repository_phid = $repository->getPHID();
|
||||||
|
|
||||||
|
$service = $repository->loadAlmanacService();
|
||||||
|
if (!$service) {
|
||||||
|
throw new Exception(pht('Failed to load repository cluster service.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$bindings = $service->getActiveBindings();
|
||||||
|
foreach ($bindings as $binding) {
|
||||||
|
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||||
|
$repository_phid,
|
||||||
|
$binding->getDevicePHID(),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task sync
|
||||||
|
*/
|
||||||
|
public function synchronizeWorkingCopyBeforeRead() {
|
||||||
|
if (!$this->shouldEnableSynchronization()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$repository_phid = $repository->getPHID();
|
||||||
|
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
$device_phid = $device->getPHID();
|
||||||
|
|
||||||
|
$read_lock = PhabricatorRepositoryWorkingCopyVersion::getReadLock(
|
||||||
|
$repository_phid,
|
||||||
|
$device_phid);
|
||||||
|
|
||||||
|
$lock_wait = phutil_units('2 minutes in seconds');
|
||||||
|
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Waiting up to %s second(s) for a cluster read lock on "%s"...',
|
||||||
|
new PhutilNumber($lock_wait),
|
||||||
|
$device->getName()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$start = PhabricatorTime::getNow();
|
||||||
|
$read_lock->lock($lock_wait);
|
||||||
|
$waited = (PhabricatorTime::getNow() - $start);
|
||||||
|
|
||||||
|
if ($waited) {
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Acquired read lock after %s second(s).',
|
||||||
|
new PhutilNumber($waited)));
|
||||||
|
} else {
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Acquired read lock immediately.'));
|
||||||
|
}
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
throw new PhutilProxyException(
|
||||||
|
pht(
|
||||||
|
'Failed to acquire read lock after waiting %s second(s). You '.
|
||||||
|
'may be able to retry later.',
|
||||||
|
new PhutilNumber($lock_wait)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
||||||
|
$repository_phid);
|
||||||
|
$versions = mpull($versions, null, 'getDevicePHID');
|
||||||
|
|
||||||
|
$this_version = idx($versions, $device_phid);
|
||||||
|
if ($this_version) {
|
||||||
|
$this_version = (int)$this_version->getRepositoryVersion();
|
||||||
|
} else {
|
||||||
|
$this_version = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($versions) {
|
||||||
|
// This is the normal case, where we have some version information and
|
||||||
|
// can identify which nodes are leaders. If the current node is not a
|
||||||
|
// leader, we want to fetch from a leader and then update our version.
|
||||||
|
|
||||||
|
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
|
||||||
|
if ($max_version > $this_version) {
|
||||||
|
$fetchable = array();
|
||||||
|
foreach ($versions as $version) {
|
||||||
|
if ($version->getRepositoryVersion() == $max_version) {
|
||||||
|
$fetchable[] = $version->getDevicePHID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->synchronizeWorkingCopyFromDevices($fetchable);
|
||||||
|
|
||||||
|
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||||
|
$repository_phid,
|
||||||
|
$device_phid,
|
||||||
|
$max_version);
|
||||||
|
} else {
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Device "%s" is already a cluster leader and does not need '.
|
||||||
|
'to be synchronized.',
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result_version = $max_version;
|
||||||
|
} else {
|
||||||
|
// If no version records exist yet, we need to be careful, because we
|
||||||
|
// can not tell which nodes are leaders.
|
||||||
|
|
||||||
|
// There might be several nodes with arbitrary existing data, and we have
|
||||||
|
// no way to tell which one has the "right" data. If we pick wrong, we
|
||||||
|
// might erase some or all of the data in the repository.
|
||||||
|
|
||||||
|
// Since this is dangeorus, we refuse to guess unless there is only one
|
||||||
|
// device. If we're the only device in the group, we obviously must be
|
||||||
|
// a leader.
|
||||||
|
|
||||||
|
$service = $repository->loadAlmanacService();
|
||||||
|
if (!$service) {
|
||||||
|
throw new Exception(pht('Failed to load repository cluster service.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$bindings = $service->getActiveBindings();
|
||||||
|
$device_map = array();
|
||||||
|
foreach ($bindings as $binding) {
|
||||||
|
$device_map[$binding->getDevicePHID()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($device_map) > 1) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Repository "%s" exists on more than one device, but no device '.
|
||||||
|
'has any repository version information. Phabricator can not '.
|
||||||
|
'guess which copy of the existing data is authoritative. Remove '.
|
||||||
|
'all but one device from service to mark the remaining device '.
|
||||||
|
'as the authority.',
|
||||||
|
$repository->getDisplayName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($device_map[$device->getPHID()])) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Repository "%s" is being synchronized on device "%s", but '.
|
||||||
|
'this device is not bound to the corresponding cluster '.
|
||||||
|
'service ("%s").',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$device->getName(),
|
||||||
|
$service->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current device is the only device in service, so it must be a
|
||||||
|
// leader. We can safely have any future nodes which come online read
|
||||||
|
// from it.
|
||||||
|
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||||
|
$repository_phid,
|
||||||
|
$device_phid,
|
||||||
|
0);
|
||||||
|
|
||||||
|
$result_version = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$read_lock->unlock();
|
||||||
|
|
||||||
|
return $result_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task sync
|
||||||
|
*/
|
||||||
|
public function synchronizeWorkingCopyBeforeWrite() {
|
||||||
|
if (!$this->shouldEnableSynchronization()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$repository_phid = $repository->getPHID();
|
||||||
|
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
$device_phid = $device->getPHID();
|
||||||
|
|
||||||
|
$table = new PhabricatorRepositoryWorkingCopyVersion();
|
||||||
|
$locked_connection = $table->establishConnection('w');
|
||||||
|
|
||||||
|
$write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock(
|
||||||
|
$repository_phid);
|
||||||
|
|
||||||
|
$write_lock->useSpecificConnection($locked_connection);
|
||||||
|
|
||||||
|
$lock_wait = phutil_units('2 minutes in seconds');
|
||||||
|
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Waiting up to %s second(s) for a cluster write lock...',
|
||||||
|
new PhutilNumber($lock_wait)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$start = PhabricatorTime::getNow();
|
||||||
|
$write_lock->lock($lock_wait);
|
||||||
|
$waited = (PhabricatorTime::getNow() - $start);
|
||||||
|
|
||||||
|
if ($waited) {
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Acquired write lock after %s second(s).',
|
||||||
|
new PhutilNumber($waited)));
|
||||||
|
} else {
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Acquired write lock immediately.'));
|
||||||
|
}
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
throw new PhutilProxyException(
|
||||||
|
pht(
|
||||||
|
'Failed to acquire write lock after waiting %s second(s). You '.
|
||||||
|
'may be able to retry later.',
|
||||||
|
new PhutilNumber($lock_wait)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
||||||
|
$repository_phid);
|
||||||
|
foreach ($versions as $version) {
|
||||||
|
if (!$version->getIsWriting()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'An previous write to this repository was interrupted; refusing '.
|
||||||
|
'new writes. This issue requires operator intervention to resolve, '.
|
||||||
|
'see "Write Interruptions" in the "Cluster: Repositories" in the '.
|
||||||
|
'documentation for instructions.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$max_version = $this->synchronizeWorkingCopyBeforeRead();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$write_lock->unlock();
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = getmypid();
|
||||||
|
$hash = Filesystem::readRandomCharacters(12);
|
||||||
|
$this->clusterWriteOwner = "{$pid}.{$hash}";
|
||||||
|
|
||||||
|
PhabricatorRepositoryWorkingCopyVersion::willWrite(
|
||||||
|
$locked_connection,
|
||||||
|
$repository_phid,
|
||||||
|
$device_phid,
|
||||||
|
array(
|
||||||
|
'userPHID' => $viewer->getPHID(),
|
||||||
|
'epoch' => PhabricatorTime::getNow(),
|
||||||
|
'devicePHID' => $device_phid,
|
||||||
|
),
|
||||||
|
$this->clusterWriteOwner);
|
||||||
|
|
||||||
|
$this->clusterWriteVersion = $max_version;
|
||||||
|
$this->clusterWriteLock = $write_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task sync
|
||||||
|
*/
|
||||||
|
public function synchronizeWorkingCopyAfterWrite() {
|
||||||
|
if (!$this->shouldEnableSynchronization()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->clusterWriteLock) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Trying to synchronize after write, but not holding a write '.
|
||||||
|
'lock!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$repository_phid = $repository->getPHID();
|
||||||
|
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
$device_phid = $device->getPHID();
|
||||||
|
|
||||||
|
// It is possible that we've lost the global lock while receiving the push.
|
||||||
|
// For example, the master database may have been restarted between the
|
||||||
|
// time we acquired the global lock and now, when the push has finished.
|
||||||
|
|
||||||
|
// We wrote a durable lock while we were holding the the global lock,
|
||||||
|
// essentially upgrading our lock. We can still safely release this upgraded
|
||||||
|
// lock even if we're no longer holding the global lock.
|
||||||
|
|
||||||
|
// If we fail to release the lock, the repository will be frozen until
|
||||||
|
// an operator can figure out what happened, so we try pretty hard to
|
||||||
|
// reconnect to the database and release the lock.
|
||||||
|
|
||||||
|
$now = PhabricatorTime::getNow();
|
||||||
|
$duration = phutil_units('5 minutes in seconds');
|
||||||
|
$try_until = $now + $duration;
|
||||||
|
|
||||||
|
$did_release = false;
|
||||||
|
$already_failed = false;
|
||||||
|
while (PhabricatorTime::getNow() <= $try_until) {
|
||||||
|
try {
|
||||||
|
// NOTE: This means we're still bumping the version when pushes fail. We
|
||||||
|
// could select only un-rejected events instead to bump a little less
|
||||||
|
// often.
|
||||||
|
|
||||||
|
$new_log = id(new PhabricatorRepositoryPushEventQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withRepositoryPHIDs(array($repository_phid))
|
||||||
|
->setLimit(1)
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
$old_version = $this->clusterWriteVersion;
|
||||||
|
if ($new_log) {
|
||||||
|
$new_version = $new_log->getID();
|
||||||
|
} else {
|
||||||
|
$new_version = $old_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhabricatorRepositoryWorkingCopyVersion::didWrite(
|
||||||
|
$repository_phid,
|
||||||
|
$device_phid,
|
||||||
|
$this->clusterWriteVersion,
|
||||||
|
$new_log->getID(),
|
||||||
|
$this->clusterWriteOwner);
|
||||||
|
$did_release = true;
|
||||||
|
break;
|
||||||
|
} catch (AphrontConnectionQueryException $ex) {
|
||||||
|
$connection_exception = $ex;
|
||||||
|
} catch (AphrontConnectionLostQueryException $ex) {
|
||||||
|
$connection_exception = $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$already_failed) {
|
||||||
|
$already_failed = true;
|
||||||
|
$this->logLine(
|
||||||
|
pht('CRITICAL. Failed to release cluster write lock!'));
|
||||||
|
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'The connection to the master database was lost while receiving '.
|
||||||
|
'the write.'));
|
||||||
|
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'This process will spend %s more second(s) attempting to '.
|
||||||
|
'recover, then give up.',
|
||||||
|
new PhutilNumber($duration)));
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($did_release) {
|
||||||
|
if ($already_failed) {
|
||||||
|
$this->logLine(
|
||||||
|
pht('RECOVERED. Link to master database was restored.'));
|
||||||
|
}
|
||||||
|
$this->logLine(pht('Released cluster write lock.'));
|
||||||
|
} else {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Failed to reconnect to master database and release held write '.
|
||||||
|
'lock ("%s") on device "%s" for repository "%s" after trying '.
|
||||||
|
'for %s seconds(s). This repository will be frozen.',
|
||||||
|
$this->clusterWriteOwner,
|
||||||
|
$device->getName(),
|
||||||
|
$this->getDisplayName(),
|
||||||
|
new PhutilNumber($duration)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can continue even if we've lost this lock, everything is still
|
||||||
|
// consistent.
|
||||||
|
try {
|
||||||
|
$this->clusterWriteLock->unlock();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clusterWriteLock = null;
|
||||||
|
$this->clusterWriteOwner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Internals )---------------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task internal
|
||||||
|
*/
|
||||||
|
private function shouldEnableSynchronization() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
|
||||||
|
$service_phid = $repository->getAlmanacServicePHID();
|
||||||
|
if (!$service_phid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: For now, this is only supported for Git.
|
||||||
|
if (!$repository->isGit()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: It may eventually make sense to try to version and synchronize
|
||||||
|
// observed repositories (so that daemons don't do reads against out-of
|
||||||
|
// date hosts), but don't bother for now.
|
||||||
|
if (!$repository->isHosted()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
if (!$device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task internal
|
||||||
|
*/
|
||||||
|
private function synchronizeWorkingCopyFromDevices(array $device_phids) {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
|
||||||
|
$service = $repository->loadAlmanacService();
|
||||||
|
if (!$service) {
|
||||||
|
throw new Exception(pht('Failed to load repository cluster service.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$device_map = array_fuse($device_phids);
|
||||||
|
$bindings = $service->getActiveBindings();
|
||||||
|
|
||||||
|
$fetchable = array();
|
||||||
|
foreach ($bindings as $binding) {
|
||||||
|
// We can't fetch from nodes which don't have the newest version.
|
||||||
|
$device_phid = $binding->getDevicePHID();
|
||||||
|
if (empty($device_map[$device_phid])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: For now, only fetch over SSH. We could support fetching over
|
||||||
|
// HTTP eventually.
|
||||||
|
if ($binding->getAlmanacPropertyValue('protocol') != 'ssh') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fetchable[] = $binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$fetchable) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Leader lost: no up-to-date nodes in repository cluster are '.
|
||||||
|
'fetchable.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$caught = null;
|
||||||
|
foreach ($fetchable as $binding) {
|
||||||
|
try {
|
||||||
|
$this->synchronizeWorkingCopyFromBinding($binding);
|
||||||
|
$caught = null;
|
||||||
|
break;
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$caught = $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($caught) {
|
||||||
|
throw $caught;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task internal
|
||||||
|
*/
|
||||||
|
private function synchronizeWorkingCopyFromBinding($binding) {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Synchronizing this device ("%s") from cluster leader ("%s") before '.
|
||||||
|
'read.',
|
||||||
|
$device->getName(),
|
||||||
|
$binding->getDevice()->getName()));
|
||||||
|
|
||||||
|
$fetch_uri = $repository->getClusterRepositoryURIFromBinding($binding);
|
||||||
|
$local_path = $repository->getLocalPath();
|
||||||
|
|
||||||
|
if ($repository->isGit()) {
|
||||||
|
if (!Filesystem::pathExists($local_path)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Repository "%s" does not have a working copy on this device '.
|
||||||
|
'yet, so it can not be synchronized. Wait for the daemons to '.
|
||||||
|
'construct one or run `bin/repository update %s` on this host '.
|
||||||
|
'("%s") to build it explicitly.',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$repository->getMonogram(),
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$argv = array(
|
||||||
|
'fetch --prune -- %s %s',
|
||||||
|
$fetch_uri,
|
||||||
|
'+refs/*:refs/*',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Exception(pht('Binding sync only supported for git!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$future = DiffusionCommandEngine::newCommandEngine($repository)
|
||||||
|
->setArgv($argv)
|
||||||
|
->setConnectAsDevice(true)
|
||||||
|
->setSudoAsDaemon(true)
|
||||||
|
->setProtocol($fetch_uri->getProtocol())
|
||||||
|
->newFuture();
|
||||||
|
|
||||||
|
$future->setCWD($local_path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$future->resolvex();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$this->logLine(
|
||||||
|
pht(
|
||||||
|
'Synchronization of "%s" from leader "%s" failed: %s',
|
||||||
|
$device->getName(),
|
||||||
|
$binding->getDevice()->getName(),
|
||||||
|
$ex->getMessage()));
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task internal
|
||||||
|
*/
|
||||||
|
private function logLine($message) {
|
||||||
|
return $this->logText("# {$message}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task internal
|
||||||
|
*/
|
||||||
|
private function logText($message) {
|
||||||
|
$log = $this->logger;
|
||||||
|
if ($log) {
|
||||||
|
$log->writeClusterEngineLogMessage($message);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
interface DiffusionRepositoryClusterEngineLogInterface {
|
||||||
|
|
||||||
|
public function writeClusterEngineLogMessage($message);
|
||||||
|
|
||||||
|
}
|
|
@ -142,7 +142,8 @@ abstract class DiffusionRequest extends Phobject {
|
||||||
|
|
||||||
$query = id(new PhabricatorRepositoryQuery())
|
$query = id(new PhabricatorRepositoryQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIdentifiers(array($identifier));
|
->withIdentifiers(array($identifier))
|
||||||
|
->needURIs(true);
|
||||||
|
|
||||||
if ($need_edit) {
|
if ($need_edit) {
|
||||||
$query->requireCapabilities(
|
$query->requireCapabilities(
|
||||||
|
|
|
@ -15,19 +15,38 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
||||||
|
|
||||||
protected function executeRepositoryOperations() {
|
protected function executeRepositoryOperations() {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
|
||||||
// This is a write, and must have write access.
|
// This is a write, and must have write access.
|
||||||
$this->requireWriteAccess();
|
$this->requireWriteAccess();
|
||||||
|
|
||||||
|
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setRepository($repository)
|
||||||
|
->setLog($this);
|
||||||
|
|
||||||
if ($this->shouldProxy()) {
|
if ($this->shouldProxy()) {
|
||||||
$command = $this->getProxyCommand();
|
$command = $this->getProxyCommand();
|
||||||
$did_synchronize = false;
|
$did_synchronize = false;
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Push received by \"%s\", forwarding to cluster host.\n",
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
||||||
|
|
||||||
$did_synchronize = true;
|
$did_synchronize = true;
|
||||||
$viewer = $this->getUser();
|
$cluster_engine->synchronizeWorkingCopyBeforeWrite();
|
||||||
$repository->synchronizeWorkingCopyBeforeWrite($viewer);
|
|
||||||
|
if ($device) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Ready to receive on cluster host \"%s\".\n",
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$caught = null;
|
$caught = null;
|
||||||
|
@ -40,7 +59,7 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
||||||
// We've committed the write (or rejected it), so we can release the lock
|
// We've committed the write (or rejected it), so we can release the lock
|
||||||
// without waiting for the client to receive the acknowledgement.
|
// without waiting for the client to receive the acknowledgement.
|
||||||
if ($did_synchronize) {
|
if ($did_synchronize) {
|
||||||
$repository->synchronizeWorkingCopyAfterWrite();
|
$cluster_engine->synchronizeWorkingCopyAfterWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($caught) {
|
if ($caught) {
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
abstract class DiffusionGitSSHWorkflow extends DiffusionSSHWorkflow {
|
abstract class DiffusionGitSSHWorkflow
|
||||||
|
extends DiffusionSSHWorkflow
|
||||||
|
implements DiffusionRepositoryClusterEngineLogInterface {
|
||||||
|
|
||||||
protected function writeError($message) {
|
protected function writeError($message) {
|
||||||
// Git assumes we'll add our own newlines.
|
// Git assumes we'll add our own newlines.
|
||||||
return parent::writeError($message."\n");
|
return parent::writeError($message."\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function writeClusterEngineLogMessage($message) {
|
||||||
|
parent::writeError($message);
|
||||||
|
$this->getErrorChannel()->update();
|
||||||
|
}
|
||||||
|
|
||||||
protected function identifyRepository() {
|
protected function identifyRepository() {
|
||||||
$args = $this->getArgs();
|
$args = $this->getArgs();
|
||||||
$path = head($args->getArg('dir'));
|
$path = head($args->getArg('dir'));
|
||||||
|
|
|
@ -15,15 +15,35 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
||||||
|
|
||||||
protected function executeRepositoryOperations() {
|
protected function executeRepositoryOperations() {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
|
$viewer = $this->getUser();
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
|
||||||
$skip_sync = $this->shouldSkipReadSynchronization();
|
$skip_sync = $this->shouldSkipReadSynchronization();
|
||||||
|
|
||||||
if ($this->shouldProxy()) {
|
if ($this->shouldProxy()) {
|
||||||
$command = $this->getProxyCommand();
|
$command = $this->getProxyCommand();
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Fetch received by \"%s\", forwarding to cluster host.\n",
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
||||||
if (!$skip_sync) {
|
if (!$skip_sync) {
|
||||||
$repository->synchronizeWorkingCopyBeforeRead();
|
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setRepository($repository)
|
||||||
|
->setLog($this)
|
||||||
|
->synchronizeWorkingCopyBeforeRead();
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$this->writeClusterEngineLogMessage(
|
||||||
|
pht(
|
||||||
|
"# Cleared to fetch on cluster host \"%s\".\n",
|
||||||
|
$device->getName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||||
|
|
|
@ -55,6 +55,21 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getCurrentDeviceName() {
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
if ($device) {
|
||||||
|
return $device->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return php_uname('n');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTargetDeviceName() {
|
||||||
|
// TODO: This should use the correct device identity.
|
||||||
|
$uri = new PhutilURI($this->proxyURI);
|
||||||
|
return $uri->getDomain();
|
||||||
|
}
|
||||||
|
|
||||||
protected function shouldProxy() {
|
protected function shouldProxy() {
|
||||||
return (bool)$this->proxyURI;
|
return (bool)$this->proxyURI;
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,14 +173,15 @@ final class PholioMockViewController extends PholioController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildDescriptionView(PholioMock $mock) {
|
private function buildDescriptionView(PholioMock $mock) {
|
||||||
|
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$properties = id(new PHUIPropertyListView())
|
$properties = id(new PHUIPropertyListView())
|
||||||
->setUser($viewer);
|
->setUser($viewer);
|
||||||
$description = $mock->getDescription();
|
$description = $mock->getDescription();
|
||||||
|
|
||||||
if (strlen($description)) {
|
if (strlen($description)) {
|
||||||
$properties->addImageContent($description);
|
$properties->addTextContent(
|
||||||
|
new PHUIRemarkupView($viewer, $description));
|
||||||
return id(new PHUIObjectBoxView())
|
return id(new PHUIObjectBoxView())
|
||||||
->setHeaderText(pht('Mock Description'))
|
->setHeaderText(pht('Mock Description'))
|
||||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||||
|
|
|
@ -92,6 +92,16 @@ final class PholioMockImagesView extends AphrontView {
|
||||||
$current_set++;
|
$current_set++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$description = $engine->getOutput($image, 'default');
|
||||||
|
if (strlen($description)) {
|
||||||
|
$description = phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'phabricator-remarkup',
|
||||||
|
),
|
||||||
|
$description);
|
||||||
|
}
|
||||||
|
|
||||||
$history_uri = '/pholio/image/history/'.$image->getID().'/';
|
$history_uri = '/pholio/image/history/'.$image->getID().'/';
|
||||||
$images[] = array(
|
$images[] = array(
|
||||||
'id' => $image->getID(),
|
'id' => $image->getID(),
|
||||||
|
@ -105,7 +115,7 @@ final class PholioMockImagesView extends AphrontView {
|
||||||
'width' => $x,
|
'width' => $x,
|
||||||
'height' => $y,
|
'height' => $y,
|
||||||
'title' => $image->getName(),
|
'title' => $image->getName(),
|
||||||
'descriptionMarkup' => $engine->getOutput($image, 'default'),
|
'descriptionMarkup' => $description,
|
||||||
'isObsolete' => (bool)$image->getIsObsolete(),
|
'isObsolete' => (bool)$image->getIsObsolete(),
|
||||||
'isImage' => $file->isViewableImage(),
|
'isImage' => $file->isViewableImage(),
|
||||||
'isViewable' => $file->isViewableInBrowser(),
|
'isViewable' => $file->isViewableInBrowser(),
|
||||||
|
|
|
@ -7,17 +7,41 @@ final class PhabricatorRepositoryType extends Phobject {
|
||||||
const REPOSITORY_TYPE_MERCURIAL = 'hg';
|
const REPOSITORY_TYPE_MERCURIAL = 'hg';
|
||||||
|
|
||||||
public static function getAllRepositoryTypes() {
|
public static function getAllRepositoryTypes() {
|
||||||
$map = array(
|
$map = self::getRepositoryTypeMap();
|
||||||
self::REPOSITORY_TYPE_GIT => pht('Git'),
|
return ipull($map, 'name');
|
||||||
self::REPOSITORY_TYPE_MERCURIAL => pht('Mercurial'),
|
|
||||||
self::REPOSITORY_TYPE_SVN => pht('Subversion'),
|
|
||||||
);
|
|
||||||
return $map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getNameForRepositoryType($type) {
|
public static function getNameForRepositoryType($type) {
|
||||||
$map = self::getAllRepositoryTypes();
|
$spec = self::getRepositoryTypeSpec($type);
|
||||||
return idx($map, $type, pht('Unknown'));
|
return idx($spec, 'name', pht('Unknown ("%s")', $type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRepositoryTypeSpec($type) {
|
||||||
|
$map = self::getRepositoryTypeMap();
|
||||||
|
return idx($map, $type, array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRepositoryTypeMap() {
|
||||||
|
return array(
|
||||||
|
self::REPOSITORY_TYPE_GIT => array(
|
||||||
|
'name' => pht('Git'),
|
||||||
|
'icon' => 'fa-git',
|
||||||
|
'create.header' => pht('Create Git Repository'),
|
||||||
|
'create.subheader' => pht('Create a new Git repository.'),
|
||||||
|
),
|
||||||
|
self::REPOSITORY_TYPE_MERCURIAL => array(
|
||||||
|
'name' => pht('Mercurial'),
|
||||||
|
'icon' => 'fa-code-fork',
|
||||||
|
'create.header' => pht('Create Mercurial Repository'),
|
||||||
|
'create.subheader' => pht('Create a new Mercurial repository.'),
|
||||||
|
),
|
||||||
|
self::REPOSITORY_TYPE_SVN => array(
|
||||||
|
'name' => pht('Subversion'),
|
||||||
|
'icon' => 'fa-database',
|
||||||
|
'create.header' => pht('Create Subversion Repository'),
|
||||||
|
'create.subheader' => pht('Create a new Subversion repository.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,7 +342,7 @@ final class PhabricatorRepositoryEditor
|
||||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
|
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY:
|
||||||
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
|
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
|
||||||
foreach ($xactions as $xaction) {
|
foreach ($xactions as $xaction) {
|
||||||
foreach ($xaction->getNewValue() as $pattern) {
|
foreach ($xaction->getNewValue() as $pattern) {
|
||||||
|
@ -636,6 +636,43 @@ final class PhabricatorRepositoryEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$old = $object->getSymbolSources();
|
||||||
|
$new = $xaction->getNewValue();
|
||||||
|
|
||||||
|
// If the viewer is adding new repositories, make sure they are
|
||||||
|
// valid and visible.
|
||||||
|
$add = array_diff($new, $old);
|
||||||
|
if (!$add) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repositories = id(new PhabricatorRepositoryQuery())
|
||||||
|
->setViewer($this->getActor())
|
||||||
|
->withPHIDs($add)
|
||||||
|
->execute();
|
||||||
|
$repositories = mpull($repositories, null, 'getPHID');
|
||||||
|
|
||||||
|
foreach ($add as $phid) {
|
||||||
|
if (isset($repositories[$phid])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht(
|
||||||
|
'Repository ("%s") does not exist, or you do not have '.
|
||||||
|
'permission to see it.',
|
||||||
|
$phid),
|
||||||
|
$xaction);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $errors;
|
return $errors;
|
||||||
|
@ -684,7 +721,10 @@ final class PhabricatorRepositoryEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->getIsNewObject()) {
|
if ($this->getIsNewObject()) {
|
||||||
$object->synchronizeWorkingCopyAfterCreation();
|
id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($this->getActor())
|
||||||
|
->setRepository($object)
|
||||||
|
->synchronizeWorkingCopyAfterCreation();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $xactions;
|
return $xactions;
|
||||||
|
|
|
@ -23,6 +23,7 @@ final class PhabricatorRepositoryPullEngine
|
||||||
|
|
||||||
public function pullRepository() {
|
public function pullRepository() {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
|
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||||
|
|
||||||
$is_hg = false;
|
$is_hg = false;
|
||||||
$is_git = false;
|
$is_git = false;
|
||||||
|
@ -96,7 +97,10 @@ final class PhabricatorRepositoryPullEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($repository->isHosted()) {
|
if ($repository->isHosted()) {
|
||||||
$repository->synchronizeWorkingCopyBeforeRead();
|
id(new DiffusionRepositoryClusterEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setRepository($repository)
|
||||||
|
->synchronizeWorkingCopyBeforeRead();
|
||||||
|
|
||||||
if ($is_git) {
|
if ($is_git) {
|
||||||
$this->installGitHook();
|
$this->installGitHook();
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorRepositoryManagementClusterizeWorkflow
|
||||||
|
extends PhabricatorRepositoryManagementWorkflow {
|
||||||
|
|
||||||
|
protected function didConstruct() {
|
||||||
|
$this
|
||||||
|
->setName('clusterize')
|
||||||
|
->setExamples('**clusterize** [options] __repository__ ...')
|
||||||
|
->setSynopsis(
|
||||||
|
pht('Convert existing repositories into cluster repositories.'))
|
||||||
|
->setArguments(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'name' => 'service',
|
||||||
|
'param' => 'service',
|
||||||
|
'help' => pht(
|
||||||
|
'Cluster repository service in Almanac to move repositories '.
|
||||||
|
'into.'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'remove-service',
|
||||||
|
'help' => pht('Take repositories out of a cluster.'),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'repositories',
|
||||||
|
'wildcard' => true,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(PhutilArgumentParser $args) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$repositories = $this->loadRepositories($args, 'repositories');
|
||||||
|
if (!$repositories) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht('Specify one or more repositories to clusterize.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$service_name = $args->getArg('service');
|
||||||
|
$remove_service = $args->getArg('remove-service');
|
||||||
|
|
||||||
|
if ($remove_service && $service_name) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht('Specify --service or --remove-service, but not both.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$service_name && !$remove_service) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht('Specify --service or --remove-service.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($remove_service) {
|
||||||
|
$service = null;
|
||||||
|
} else {
|
||||||
|
$service = id(new AlmanacServiceQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withNames(array($service_name))
|
||||||
|
->withServiceTypes(
|
||||||
|
array(
|
||||||
|
AlmanacClusterRepositoryServiceType::SERVICETYPE,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$service) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'No repository service "%s" exists.',
|
||||||
|
$service_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($service) {
|
||||||
|
$service_phid = $service->getPHID();
|
||||||
|
} else {
|
||||||
|
$service_phid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content_source = $this->newContentSource();
|
||||||
|
$diffusion_phid = id(new PhabricatorDiffusionApplication())->getPHID();
|
||||||
|
|
||||||
|
foreach ($repositories as $repository) {
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorRepositoryTransaction())
|
||||||
|
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_SERVICE)
|
||||||
|
->setNewValue($service_phid);
|
||||||
|
|
||||||
|
id(new PhabricatorRepositoryEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setActingAsPHID($diffusion_phid)
|
||||||
|
->setContentSource($content_source)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->applyTransactions($repository, $xactions);
|
||||||
|
|
||||||
|
if ($service) {
|
||||||
|
echo tsprintf(
|
||||||
|
"%s\n",
|
||||||
|
pht(
|
||||||
|
'Moved repository "%s" to cluster service "%s".',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$service->getName()));
|
||||||
|
} else {
|
||||||
|
echo tsprintf(
|
||||||
|
"%s\n",
|
||||||
|
pht(
|
||||||
|
'Removed repository "%s" from cluster service.',
|
||||||
|
$repository->getDisplayName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -66,7 +66,7 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow
|
||||||
} else {
|
} else {
|
||||||
$dst = $to.substr($src, strlen($from));
|
$dst = $to.substr($src, strlen($from));
|
||||||
|
|
||||||
$row['action'] = phutil_console_format('**%s**', pht('Move'));
|
$row['action'] = tsprintf('**%s**', pht('Move'));
|
||||||
$row['dst'] = $dst;
|
$row['dst'] = $dst;
|
||||||
$row['move'] = true;
|
$row['move'] = true;
|
||||||
$any_changes = true;
|
$any_changes = true;
|
||||||
|
@ -94,7 +94,7 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow
|
||||||
->addColumn(
|
->addColumn(
|
||||||
'dst',
|
'dst',
|
||||||
array(
|
array(
|
||||||
'title' => pht('dst'),
|
'title' => pht('Dst'),
|
||||||
))
|
))
|
||||||
->setBorders(true);
|
->setBorders(true);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorRepositoryURIPHIDType extends PhabricatorPHIDType {
|
||||||
|
|
||||||
|
const TYPECONST = 'RURI';
|
||||||
|
|
||||||
|
public function getTypeName() {
|
||||||
|
return pht('Repository URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newObject() {
|
||||||
|
return new PhabricatorRepositoryURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPHIDTypeApplicationClass() {
|
||||||
|
return 'PhabricatorDiffusionApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildQueryForObjects(
|
||||||
|
PhabricatorObjectQuery $query,
|
||||||
|
array $phids) {
|
||||||
|
|
||||||
|
return id(new PhabricatorRepositoryURIQuery())
|
||||||
|
->withPHIDs($phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadHandles(
|
||||||
|
PhabricatorHandleQuery $query,
|
||||||
|
array $handles,
|
||||||
|
array $objects) {
|
||||||
|
|
||||||
|
foreach ($handles as $phid => $handle) {
|
||||||
|
$uri = $objects[$phid];
|
||||||
|
|
||||||
|
$handle->setName(
|
||||||
|
pht('URI %d %s', $uri->getID(), $uri->getDisplayURI()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ final class PhabricatorRepositoryQuery
|
||||||
private $needMostRecentCommits;
|
private $needMostRecentCommits;
|
||||||
private $needCommitCounts;
|
private $needCommitCounts;
|
||||||
private $needProjectPHIDs;
|
private $needProjectPHIDs;
|
||||||
|
private $needURIs;
|
||||||
|
|
||||||
public function withIDs(array $ids) {
|
public function withIDs(array $ids) {
|
||||||
$this->ids = $ids;
|
$this->ids = $ids;
|
||||||
|
@ -148,6 +149,11 @@ final class PhabricatorRepositoryQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function needURIs($need_uris) {
|
||||||
|
$this->needURIs = $need_uris;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getBuiltinOrders() {
|
public function getBuiltinOrders() {
|
||||||
return array(
|
return array(
|
||||||
'committed' => array(
|
'committed' => array(
|
||||||
|
@ -348,6 +354,20 @@ final class PhabricatorRepositoryQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
if ($this->needURIs) {
|
||||||
|
$uris = id(new PhabricatorRepositoryURIQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withRepositories($repositories)
|
||||||
|
->execute();
|
||||||
|
$uri_groups = mgroup($uris, 'getRepositoryPHID');
|
||||||
|
foreach ($repositories as $repository) {
|
||||||
|
$repository_uris = idx($uri_groups, $repository->getPHID(), array());
|
||||||
|
$repository->attachURIs($repository_uris);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $repositories;
|
return $repositories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorRepositoryURIQuery
|
||||||
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||||
|
|
||||||
|
private $ids;
|
||||||
|
private $phids;
|
||||||
|
private $repositoryPHIDs;
|
||||||
|
private $repositories = array();
|
||||||
|
|
||||||
|
public function withIDs(array $ids) {
|
||||||
|
$this->ids = $ids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPHIDs(array $phids) {
|
||||||
|
$this->phids = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withRepositoryPHIDs(array $phids) {
|
||||||
|
$this->repositoryPHIDs = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withRepositories(array $repositories) {
|
||||||
|
$repositories = mpull($repositories, null, 'getPHID');
|
||||||
|
$this->withRepositoryPHIDs(array_keys($repositories));
|
||||||
|
$this->repositories = $repositories;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withObjectHashes(array $hashes) {
|
||||||
|
$this->objectHashes = $hashes;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newResultObject() {
|
||||||
|
return new PhabricatorRepositoryURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadPage() {
|
||||||
|
return $this->loadStandardPage($this->newResultObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||||
|
$where = parent::buildWhereClauseParts($conn);
|
||||||
|
|
||||||
|
if ($this->ids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'id IN (%Ld)',
|
||||||
|
$this->ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->phids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'phid IN (%Ls)',
|
||||||
|
$this->phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->repositoryPHIDs !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'repositoryPHID IN (%Ls)',
|
||||||
|
$this->repositoryPHIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $where;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function willFilterPage(array $uris) {
|
||||||
|
$repositories = $this->repositories;
|
||||||
|
|
||||||
|
$repository_phids = mpull($uris, 'getRepositoryPHID');
|
||||||
|
$repository_phids = array_fuse($repository_phids);
|
||||||
|
$repository_phids = array_diff_key($repository_phids, $repositories);
|
||||||
|
|
||||||
|
if ($repository_phids) {
|
||||||
|
$more_repositories = id(new PhabricatorRepositoryQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->withPHIDs($repository_phids)
|
||||||
|
->execute();
|
||||||
|
$repositories += mpull($more_repositories, null, 'getPHID');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uris as $key => $uri) {
|
||||||
|
$repository_phid = $uri->getRepositoryPHID();
|
||||||
|
$repository = idx($repositories, $repository_phid);
|
||||||
|
if (!$repository) {
|
||||||
|
$this->didRejectResult($uri);
|
||||||
|
unset($uris[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$uri->attachRepository($repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryApplicationClass() {
|
||||||
|
return 'PhabricatorDiffusionApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorRepositoryURITransactionQuery
|
||||||
|
extends PhabricatorApplicationTransactionQuery {
|
||||||
|
|
||||||
|
public function getTemplateApplicationTransaction() {
|
||||||
|
return new PhabricatorRepositoryURITransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -68,9 +68,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
private $projectPHIDs = self::ATTACHABLE;
|
private $projectPHIDs = self::ATTACHABLE;
|
||||||
private $uris = self::ATTACHABLE;
|
private $uris = self::ATTACHABLE;
|
||||||
|
|
||||||
private $clusterWriteLock;
|
|
||||||
private $clusterWriteVersion;
|
|
||||||
|
|
||||||
|
|
||||||
public static function initializeNewRepository(PhabricatorUser $actor) {
|
public static function initializeNewRepository(PhabricatorUser $actor) {
|
||||||
$app = id(new PhabricatorApplicationQuery())
|
$app = id(new PhabricatorApplicationQuery())
|
||||||
|
@ -1832,8 +1829,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
|
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
"The URI protocol is unrecognized. It should begin ".
|
'The URI protocol is unrecognized. It should begin with '.
|
||||||
"'%s', '%s', '%s', '%s', '%s', '%s', or be in the form '%s'.",
|
'"%s", "%s", "%s", "%s", "%s", "%s", or be in the form "%s".',
|
||||||
'ssh://',
|
'ssh://',
|
||||||
'http://',
|
'http://',
|
||||||
'https://',
|
'https://',
|
||||||
|
@ -2182,7 +2179,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
$uris = array();
|
$uris = array();
|
||||||
foreach ($protocol_map as $protocol => $proto_supported) {
|
foreach ($protocol_map as $protocol => $proto_supported) {
|
||||||
foreach ($identifier_map as $identifier => $id_supported) {
|
foreach ($identifier_map as $identifier => $id_supported) {
|
||||||
$uris[] = PhabricatorRepositoryURI::initializeNewURI($this)
|
$uris[] = PhabricatorRepositoryURI::initializeNewURI()
|
||||||
|
->setRepositoryPHID($this->getPHID())
|
||||||
|
->attachRepository($this)
|
||||||
->setBuiltinProtocol($protocol)
|
->setBuiltinProtocol($protocol)
|
||||||
->setBuiltinIdentifier($identifier)
|
->setBuiltinIdentifier($identifier)
|
||||||
->setIsDisabled(!$proto_supported || !$id_supported);
|
->setIsDisabled(!$proto_supported || !$id_supported);
|
||||||
|
@ -2193,379 +2192,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Cluster Synchronization )-------------------------------------------- */
|
public function getClusterRepositoryURIFromBinding(
|
||||||
|
|
||||||
|
|
||||||
private function shouldEnableSynchronization() {
|
|
||||||
$service_phid = $this->getAlmanacServicePHID();
|
|
||||||
if (!$service_phid) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: For now, this is only supported for Git.
|
|
||||||
if (!$this->isGit()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: It may eventually make sense to try to version and synchronize
|
|
||||||
// observed repositories (so that daemons don't do reads against out-of
|
|
||||||
// date hosts), but don't bother for now.
|
|
||||||
if (!$this->isHosted()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
|
||||||
if (!$device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronize repository version information after creating a repository.
|
|
||||||
*
|
|
||||||
* This initializes working copy versions for all currently bound devices to
|
|
||||||
* 0, so that we don't get stuck making an ambiguous choice about which
|
|
||||||
* devices are leaders when we later synchronize before a read.
|
|
||||||
*
|
|
||||||
* @task sync
|
|
||||||
*/
|
|
||||||
public function synchronizeWorkingCopyAfterCreation() {
|
|
||||||
if (!$this->shouldEnableSynchronization()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$repository_phid = $this->getPHID();
|
|
||||||
|
|
||||||
$service = $this->loadAlmanacService();
|
|
||||||
if (!$service) {
|
|
||||||
throw new Exception(pht('Failed to load repository cluster service.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$bindings = $service->getActiveBindings();
|
|
||||||
foreach ($bindings as $binding) {
|
|
||||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
|
||||||
$repository_phid,
|
|
||||||
$binding->getDevicePHID(),
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @task sync
|
|
||||||
*/
|
|
||||||
public function synchronizeWorkingCopyBeforeRead() {
|
|
||||||
if (!$this->shouldEnableSynchronization()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$repository_phid = $this->getPHID();
|
|
||||||
|
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
|
||||||
$device_phid = $device->getPHID();
|
|
||||||
|
|
||||||
$read_lock = PhabricatorRepositoryWorkingCopyVersion::getReadLock(
|
|
||||||
$repository_phid,
|
|
||||||
$device_phid);
|
|
||||||
|
|
||||||
// TODO: Raise a more useful exception if we fail to grab this lock.
|
|
||||||
$read_lock->lock(phutil_units('2 minutes in seconds'));
|
|
||||||
|
|
||||||
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
|
||||||
$repository_phid);
|
|
||||||
$versions = mpull($versions, null, 'getDevicePHID');
|
|
||||||
|
|
||||||
$this_version = idx($versions, $device_phid);
|
|
||||||
if ($this_version) {
|
|
||||||
$this_version = (int)$this_version->getRepositoryVersion();
|
|
||||||
} else {
|
|
||||||
$this_version = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($versions) {
|
|
||||||
// This is the normal case, where we have some version information and
|
|
||||||
// can identify which nodes are leaders. If the current node is not a
|
|
||||||
// leader, we want to fetch from a leader and then update our version.
|
|
||||||
|
|
||||||
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
|
|
||||||
if ($max_version > $this_version) {
|
|
||||||
$fetchable = array();
|
|
||||||
foreach ($versions as $version) {
|
|
||||||
if ($version->getRepositoryVersion() == $max_version) {
|
|
||||||
$fetchable[] = $version->getDevicePHID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->synchronizeWorkingCopyFromDevices($fetchable);
|
|
||||||
|
|
||||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
|
||||||
$repository_phid,
|
|
||||||
$device_phid,
|
|
||||||
$max_version);
|
|
||||||
}
|
|
||||||
|
|
||||||
$result_version = $max_version;
|
|
||||||
} else {
|
|
||||||
// If no version records exist yet, we need to be careful, because we
|
|
||||||
// can not tell which nodes are leaders.
|
|
||||||
|
|
||||||
// There might be several nodes with arbitrary existing data, and we have
|
|
||||||
// no way to tell which one has the "right" data. If we pick wrong, we
|
|
||||||
// might erase some or all of the data in the repository.
|
|
||||||
|
|
||||||
// Since this is dangeorus, we refuse to guess unless there is only one
|
|
||||||
// device. If we're the only device in the group, we obviously must be
|
|
||||||
// a leader.
|
|
||||||
|
|
||||||
$service = $this->loadAlmanacService();
|
|
||||||
if (!$service) {
|
|
||||||
throw new Exception(pht('Failed to load repository cluster service.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$bindings = $service->getActiveBindings();
|
|
||||||
$device_map = array();
|
|
||||||
foreach ($bindings as $binding) {
|
|
||||||
$device_map[$binding->getDevicePHID()] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($device_map) > 1) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Repository "%s" exists on more than one device, but no device '.
|
|
||||||
'has any repository version information. Phabricator can not '.
|
|
||||||
'guess which copy of the existing data is authoritative. Remove '.
|
|
||||||
'all but one device from service to mark the remaining device '.
|
|
||||||
'as the authority.',
|
|
||||||
$this->getDisplayName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($device_map[$device->getPHID()])) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Repository "%s" is being synchronized on device "%s", but '.
|
|
||||||
'this device is not bound to the corresponding cluster '.
|
|
||||||
'service ("%s").',
|
|
||||||
$this->getDisplayName(),
|
|
||||||
$device->getName(),
|
|
||||||
$service->getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The current device is the only device in service, so it must be a
|
|
||||||
// leader. We can safely have any future nodes which come online read
|
|
||||||
// from it.
|
|
||||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
|
||||||
$repository_phid,
|
|
||||||
$device_phid,
|
|
||||||
0);
|
|
||||||
|
|
||||||
$result_version = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$read_lock->unlock();
|
|
||||||
|
|
||||||
return $result_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @task sync
|
|
||||||
*/
|
|
||||||
public function synchronizeWorkingCopyBeforeWrite(
|
|
||||||
PhabricatorUser $actor) {
|
|
||||||
if (!$this->shouldEnableSynchronization()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$repository_phid = $this->getPHID();
|
|
||||||
|
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
|
||||||
$device_phid = $device->getPHID();
|
|
||||||
|
|
||||||
$write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock(
|
|
||||||
$repository_phid);
|
|
||||||
|
|
||||||
// TODO: Raise a more useful exception if we fail to grab this lock.
|
|
||||||
$write_lock->lock(phutil_units('2 minutes in seconds'));
|
|
||||||
|
|
||||||
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
|
||||||
$repository_phid);
|
|
||||||
foreach ($versions as $version) {
|
|
||||||
if (!$version->getIsWriting()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'An previous write to this repository was interrupted; refusing '.
|
|
||||||
'new writes. This issue resolves operator intervention to resolve, '.
|
|
||||||
'see "Write Interruptions" in the "Cluster: Repositories" in the '.
|
|
||||||
'documentation for instructions.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$max_version = $this->synchronizeWorkingCopyBeforeRead();
|
|
||||||
} catch (Exception $ex) {
|
|
||||||
$write_lock->unlock();
|
|
||||||
throw $ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
PhabricatorRepositoryWorkingCopyVersion::willWrite(
|
|
||||||
$repository_phid,
|
|
||||||
$device_phid,
|
|
||||||
array(
|
|
||||||
'userPHID' => $actor->getPHID(),
|
|
||||||
'epoch' => PhabricatorTime::getNow(),
|
|
||||||
'devicePHID' => $device_phid,
|
|
||||||
));
|
|
||||||
|
|
||||||
$this->clusterWriteVersion = $max_version;
|
|
||||||
$this->clusterWriteLock = $write_lock;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @task sync
|
|
||||||
*/
|
|
||||||
public function synchronizeWorkingCopyAfterWrite() {
|
|
||||||
if (!$this->shouldEnableSynchronization()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->clusterWriteLock) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Trying to synchronize after write, but not holding a write '.
|
|
||||||
'lock!'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$repository_phid = $this->getPHID();
|
|
||||||
|
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
|
||||||
$device_phid = $device->getPHID();
|
|
||||||
|
|
||||||
// NOTE: This means we're still bumping the version when pushes fail. We
|
|
||||||
// could select only un-rejected events instead to bump a little less
|
|
||||||
// often.
|
|
||||||
|
|
||||||
$new_log = id(new PhabricatorRepositoryPushEventQuery())
|
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
||||||
->withRepositoryPHIDs(array($repository_phid))
|
|
||||||
->setLimit(1)
|
|
||||||
->executeOne();
|
|
||||||
|
|
||||||
$old_version = $this->clusterWriteVersion;
|
|
||||||
if ($new_log) {
|
|
||||||
$new_version = $new_log->getID();
|
|
||||||
} else {
|
|
||||||
$new_version = $old_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
PhabricatorRepositoryWorkingCopyVersion::didWrite(
|
|
||||||
$repository_phid,
|
|
||||||
$device_phid,
|
|
||||||
$this->clusterWriteVersion,
|
|
||||||
$new_log->getID());
|
|
||||||
|
|
||||||
$this->clusterWriteLock->unlock();
|
|
||||||
$this->clusterWriteLock = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @task sync
|
|
||||||
*/
|
|
||||||
private function synchronizeWorkingCopyFromDevices(array $device_phids) {
|
|
||||||
$service = $this->loadAlmanacService();
|
|
||||||
if (!$service) {
|
|
||||||
throw new Exception(pht('Failed to load repository cluster service.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$device_map = array_fuse($device_phids);
|
|
||||||
$bindings = $service->getActiveBindings();
|
|
||||||
|
|
||||||
$fetchable = array();
|
|
||||||
foreach ($bindings as $binding) {
|
|
||||||
// We can't fetch from nodes which don't have the newest version.
|
|
||||||
$device_phid = $binding->getDevicePHID();
|
|
||||||
if (empty($device_map[$device_phid])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: For now, only fetch over SSH. We could support fetching over
|
|
||||||
// HTTP eventually.
|
|
||||||
if ($binding->getAlmanacPropertyValue('protocol') != 'ssh') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fetchable[] = $binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$fetchable) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Leader lost: no up-to-date nodes in repository cluster are '.
|
|
||||||
'fetchable.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$caught = null;
|
|
||||||
foreach ($fetchable as $binding) {
|
|
||||||
try {
|
|
||||||
$this->synchronizeWorkingCopyFromBinding($binding);
|
|
||||||
$caught = null;
|
|
||||||
break;
|
|
||||||
} catch (Exception $ex) {
|
|
||||||
$caught = $ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($caught) {
|
|
||||||
throw $caught;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function synchronizeWorkingCopyFromBinding($binding) {
|
|
||||||
$fetch_uri = $this->getClusterRepositoryURIFromBinding($binding);
|
|
||||||
$local_path = $this->getLocalPath();
|
|
||||||
|
|
||||||
if ($this->isGit()) {
|
|
||||||
if (!Filesystem::pathExists($local_path)) {
|
|
||||||
$device = AlmanacKeys::getLiveDevice();
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Repository "%s" does not have a working copy on this device '.
|
|
||||||
'yet, so it can not be synchronized. Wait for the daemons to '.
|
|
||||||
'construct one or run `bin/repository update %s` on this host '.
|
|
||||||
'("%s") to build it explicitly.',
|
|
||||||
$this->getDisplayName(),
|
|
||||||
$this->getMonogram(),
|
|
||||||
$device->getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$argv = array(
|
|
||||||
'fetch --prune -- %s %s',
|
|
||||||
$fetch_uri,
|
|
||||||
'+refs/*:refs/*',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Exception(pht('Binding sync only supported for git!'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$future = DiffusionCommandEngine::newCommandEngine($this)
|
|
||||||
->setArgv($argv)
|
|
||||||
->setConnectAsDevice(true)
|
|
||||||
->setSudoAsDaemon(true)
|
|
||||||
->setProtocol($fetch_uri->getProtocol())
|
|
||||||
->newFuture();
|
|
||||||
|
|
||||||
$future->setCWD($local_path);
|
|
||||||
|
|
||||||
$future->resolvex();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getClusterRepositoryURIFromBinding(
|
|
||||||
AlmanacBinding $binding) {
|
AlmanacBinding $binding) {
|
||||||
$protocol = $binding->getAlmanacPropertyValue('protocol');
|
$protocol = $binding->getAlmanacPropertyValue('protocol');
|
||||||
if ($protocol === null) {
|
if ($protocol === null) {
|
||||||
|
@ -2613,8 +2240,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -( Symbols )-------------------------------------------------------------*/
|
/* -( Symbols )-------------------------------------------------------------*/
|
||||||
|
|
||||||
public function getSymbolSources() {
|
public function getSymbolSources() {
|
||||||
|
@ -2829,6 +2454,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
->setKey('shortName')
|
->setKey('shortName')
|
||||||
->setType('string')
|
->setType('string')
|
||||||
->setDescription(pht('Unique short name, if the repository has one.')),
|
->setDescription(pht('Unique short name, if the repository has one.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('status')
|
||||||
|
->setType('string')
|
||||||
|
->setDescription(pht('Active or inactive status.')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2838,11 +2467,15 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
||||||
'vcs' => $this->getVersionControlSystem(),
|
'vcs' => $this->getVersionControlSystem(),
|
||||||
'callsign' => $this->getCallsign(),
|
'callsign' => $this->getCallsign(),
|
||||||
'shortName' => $this->getRepositorySlug(),
|
'shortName' => $this->getRepositorySlug(),
|
||||||
|
'status' => $this->getStatus(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getConduitSearchAttachments() {
|
public function getConduitSearchAttachments() {
|
||||||
return array();
|
return array(
|
||||||
|
id(new DiffusionRepositoryURIsSearchEngineAttachment())
|
||||||
|
->setAttachmentKey('uris'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorRepositoryURI
|
final class PhabricatorRepositoryURI
|
||||||
extends PhabricatorRepositoryDAO {
|
extends PhabricatorRepositoryDAO
|
||||||
|
implements
|
||||||
|
PhabricatorApplicationTransactionInterface,
|
||||||
|
PhabricatorPolicyInterface,
|
||||||
|
PhabricatorExtendedPolicyInterface,
|
||||||
|
PhabricatorConduitResultInterface {
|
||||||
|
|
||||||
protected $repositoryPHID;
|
protected $repositoryPHID;
|
||||||
protected $uri;
|
protected $uri;
|
||||||
|
@ -58,15 +63,18 @@ final class PhabricatorRepositoryURI
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function initializeNewURI(PhabricatorRepository $repository) {
|
public static function initializeNewURI() {
|
||||||
return id(new self())
|
return id(new self())
|
||||||
->attachRepository($repository)
|
|
||||||
->setRepositoryPHID($repository->getPHID())
|
|
||||||
->setIoType(self::IO_DEFAULT)
|
->setIoType(self::IO_DEFAULT)
|
||||||
->setDisplayType(self::DISPLAY_DEFAULT)
|
->setDisplayType(self::DISPLAY_DEFAULT)
|
||||||
->setIsDisabled(0);
|
->setIsDisabled(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function generatePHID() {
|
||||||
|
return PhabricatorPHID::generateNewPHID(
|
||||||
|
PhabricatorRepositoryURIPHIDType::TYPECONST);
|
||||||
|
}
|
||||||
|
|
||||||
public function attachRepository(PhabricatorRepository $repository) {
|
public function attachRepository(PhabricatorRepository $repository) {
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -85,6 +93,7 @@ final class PhabricatorRepositoryURI
|
||||||
$this->getBuiltinProtocol(),
|
$this->getBuiltinProtocol(),
|
||||||
$this->getBuiltinIdentifier(),
|
$this->getBuiltinIdentifier(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return implode('.', $parts);
|
return implode('.', $parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,58 +104,57 @@ final class PhabricatorRepositoryURI
|
||||||
public function getEffectiveDisplayType() {
|
public function getEffectiveDisplayType() {
|
||||||
$display = $this->getDisplayType();
|
$display = $this->getDisplayType();
|
||||||
|
|
||||||
if ($display != self::IO_DEFAULT) {
|
if ($display != self::DISPLAY_DEFAULT) {
|
||||||
return $display;
|
return $display;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->getDefaultDisplayType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultDisplayType() {
|
||||||
switch ($this->getEffectiveIOType()) {
|
switch ($this->getEffectiveIOType()) {
|
||||||
case self::IO_MIRROR:
|
case self::IO_MIRROR:
|
||||||
case self::IO_OBSERVE:
|
case self::IO_OBSERVE:
|
||||||
return self::DISPLAY_NEVER;
|
|
||||||
case self::IO_NONE:
|
case self::IO_NONE:
|
||||||
if ($this->isBuiltin()) {
|
return self::DISPLAY_NEVER;
|
||||||
return self::DISPLAY_NEVER;
|
|
||||||
} else {
|
|
||||||
return self::DISPLAY_ALWAYS;
|
|
||||||
}
|
|
||||||
case self::IO_READ:
|
case self::IO_READ:
|
||||||
case self::IO_READWRITE:
|
case self::IO_READWRITE:
|
||||||
// By default, only show the "best" version of the builtin URI, not the
|
// By default, only show the "best" version of the builtin URI, not the
|
||||||
// other redundant versions.
|
// other redundant versions.
|
||||||
if ($this->isBuiltin()) {
|
$repository = $this->getRepository();
|
||||||
$repository = $this->getRepository();
|
$other_uris = $repository->getURIs();
|
||||||
$other_uris = $repository->getURIs();
|
|
||||||
|
|
||||||
$identifier_value = array(
|
$identifier_value = array(
|
||||||
self::BUILTIN_IDENTIFIER_CALLSIGN => 3,
|
self::BUILTIN_IDENTIFIER_CALLSIGN => 3,
|
||||||
self::BUILTIN_IDENTIFIER_SHORTNAME => 2,
|
self::BUILTIN_IDENTIFIER_SHORTNAME => 2,
|
||||||
self::BUILTIN_IDENTIFIER_ID => 1,
|
self::BUILTIN_IDENTIFIER_ID => 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
$have_identifiers = array();
|
$have_identifiers = array();
|
||||||
foreach ($other_uris as $other_uri) {
|
foreach ($other_uris as $other_uri) {
|
||||||
if ($other_uri->getIsDisabled()) {
|
if ($other_uri->getIsDisabled()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
$identifier = $other_uri->getBuiltinIdentifier();
|
|
||||||
if (!$identifier) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$have_identifiers[$identifier] = $identifier_value[$identifier];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$best_identifier = max($have_identifiers);
|
$identifier = $other_uri->getBuiltinIdentifier();
|
||||||
$this_identifier = $identifier_value[$this->getBuiltinIdentifier()];
|
if (!$identifier) {
|
||||||
|
continue;
|
||||||
if ($this_identifier < $best_identifier) {
|
|
||||||
return self::DISPLAY_NEVER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$have_identifiers[$identifier] = $identifier_value[$identifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
$best_identifier = max($have_identifiers);
|
||||||
|
$this_identifier = $identifier_value[$this->getBuiltinIdentifier()];
|
||||||
|
|
||||||
|
if ($this_identifier < $best_identifier) {
|
||||||
|
return self::DISPLAY_NEVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::DISPLAY_ALWAYS;
|
return self::DISPLAY_ALWAYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::DISPLAY_NEVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,6 +165,10 @@ final class PhabricatorRepositoryURI
|
||||||
return $io;
|
return $io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->getDefaultIOType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultIOType() {
|
||||||
if ($this->isBuiltin()) {
|
if ($this->isBuiltin()) {
|
||||||
$repository = $this->getRepository();
|
$repository = $this->getRepository();
|
||||||
$other_uris = $repository->getURIs();
|
$other_uris = $repository->getURIs();
|
||||||
|
@ -176,7 +188,7 @@ final class PhabricatorRepositoryURI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::IO_IGNORE;
|
return self::IO_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -298,4 +310,290 @@ final class PhabricatorRepositoryURI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getViewURI() {
|
||||||
|
$id = $this->getID();
|
||||||
|
return $this->getRepository()->getPathURI("uri/view/{$id}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditURI() {
|
||||||
|
$id = $this->getID();
|
||||||
|
return $this->getRepository()->getPathURI("uri/edit/{$id}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvailableIOTypeOptions() {
|
||||||
|
$options = array(
|
||||||
|
self::IO_DEFAULT,
|
||||||
|
self::IO_NONE,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->isBuiltin()) {
|
||||||
|
$options[] = self::IO_READ;
|
||||||
|
$options[] = self::IO_WRITE;
|
||||||
|
} else {
|
||||||
|
$options[] = self::IO_OBSERVE;
|
||||||
|
$options[] = self::IO_MIRROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = array();
|
||||||
|
$io_map = self::getIOTypeMap();
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$spec = idx($io_map, $option, array());
|
||||||
|
|
||||||
|
$label = idx($spec, 'label', $option);
|
||||||
|
$short = idx($spec, 'short');
|
||||||
|
|
||||||
|
$name = pht('%s: %s', $label, $short);
|
||||||
|
$map[$option] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvailableDisplayTypeOptions() {
|
||||||
|
$options = array(
|
||||||
|
self::DISPLAY_DEFAULT,
|
||||||
|
self::DISPLAY_ALWAYS,
|
||||||
|
self::DISPLAY_NEVER,
|
||||||
|
);
|
||||||
|
|
||||||
|
$map = array();
|
||||||
|
$display_map = self::getDisplayTypeMap();
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$spec = idx($display_map, $option, array());
|
||||||
|
|
||||||
|
$label = idx($spec, 'label', $option);
|
||||||
|
$short = idx($spec, 'short');
|
||||||
|
|
||||||
|
$name = pht('%s: %s', $label, $short);
|
||||||
|
$map[$option] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getIOTypeMap() {
|
||||||
|
return array(
|
||||||
|
self::IO_DEFAULT => array(
|
||||||
|
'label' => pht('Default'),
|
||||||
|
'short' => pht('Use default behavior.'),
|
||||||
|
),
|
||||||
|
self::IO_OBSERVE => array(
|
||||||
|
'icon' => 'fa-download',
|
||||||
|
'color' => 'green',
|
||||||
|
'label' => pht('Observe'),
|
||||||
|
'note' => pht(
|
||||||
|
'Phabricator will observe changes to this URI and copy them.'),
|
||||||
|
'short' => pht('Copy from a remote.'),
|
||||||
|
),
|
||||||
|
self::IO_MIRROR => array(
|
||||||
|
'icon' => 'fa-upload',
|
||||||
|
'color' => 'green',
|
||||||
|
'label' => pht('Mirror'),
|
||||||
|
'note' => pht(
|
||||||
|
'Phabricator will push a copy of any changes to this URI.'),
|
||||||
|
'short' => pht('Push a copy to a remote.'),
|
||||||
|
),
|
||||||
|
self::IO_NONE => array(
|
||||||
|
'icon' => 'fa-times',
|
||||||
|
'color' => 'grey',
|
||||||
|
'label' => pht('No I/O'),
|
||||||
|
'note' => pht(
|
||||||
|
'Phabricator will not push or pull any changes to this URI.'),
|
||||||
|
'short' => pht('Do not perform any I/O.'),
|
||||||
|
),
|
||||||
|
self::IO_READ => array(
|
||||||
|
'icon' => 'fa-folder',
|
||||||
|
'color' => 'blue',
|
||||||
|
'label' => pht('Read Only'),
|
||||||
|
'note' => pht(
|
||||||
|
'Phabricator will serve a read-only copy of the repository from '.
|
||||||
|
'this URI.'),
|
||||||
|
'short' => pht('Serve repository in read-only mode.'),
|
||||||
|
),
|
||||||
|
self::IO_READWRITE => array(
|
||||||
|
'icon' => 'fa-folder-open',
|
||||||
|
'color' => 'blue',
|
||||||
|
'label' => pht('Read/Write'),
|
||||||
|
'note' => pht(
|
||||||
|
'Phabricator will serve a read/write copy of the repository from '.
|
||||||
|
'this URI.'),
|
||||||
|
'short' => pht('Serve repository in read/write mode.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getDisplayTypeMap() {
|
||||||
|
return array(
|
||||||
|
self::DISPLAY_DEFAULT => array(
|
||||||
|
'label' => pht('Default'),
|
||||||
|
'short' => pht('Use default behavior.'),
|
||||||
|
),
|
||||||
|
self::DISPLAY_ALWAYS => array(
|
||||||
|
'icon' => 'fa-eye',
|
||||||
|
'color' => 'green',
|
||||||
|
'label' => pht('Visible'),
|
||||||
|
'note' => pht('This URI will be shown to users as a clone URI.'),
|
||||||
|
'short' => pht('Show as a clone URI.'),
|
||||||
|
),
|
||||||
|
self::DISPLAY_NEVER => array(
|
||||||
|
'icon' => 'fa-eye-slash',
|
||||||
|
'color' => 'grey',
|
||||||
|
'label' => pht('Hidden'),
|
||||||
|
'note' => pht(
|
||||||
|
'This URI will be hidden from users.'),
|
||||||
|
'short' => pht('Do not show as a clone URI.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getApplicationTransactionEditor() {
|
||||||
|
return new DiffusionURIEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionObject() {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionTemplate() {
|
||||||
|
return new PhabricatorRepositoryURITransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function willRenderTimeline(
|
||||||
|
PhabricatorApplicationTransactionView $timeline,
|
||||||
|
AphrontRequest $request) {
|
||||||
|
return $timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
switch ($capability) {
|
||||||
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||||
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||||
|
return PhabricatorPolicies::getMostOpenPolicy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAutomaticCapability($capability) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
||||||
|
$extended = array();
|
||||||
|
|
||||||
|
switch ($capability) {
|
||||||
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||||
|
// To edit a repository URI, you must be able to edit the
|
||||||
|
// corresponding repository.
|
||||||
|
$extended[] = array($this->getRepository(), $capability);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorConduitResultInterface )---------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getFieldSpecificationsForConduit() {
|
||||||
|
return array(
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('repositoryPHID')
|
||||||
|
->setType('phid')
|
||||||
|
->setDescription(pht('The associated repository PHID.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('uri')
|
||||||
|
->setType('map<string, string>')
|
||||||
|
->setDescription(pht('The raw and effective URI.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('io')
|
||||||
|
->setType('map<string, const>')
|
||||||
|
->setDescription(
|
||||||
|
pht('The raw, default, and effective I/O Type settings.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('display')
|
||||||
|
->setType('map<string, const>')
|
||||||
|
->setDescription(
|
||||||
|
pht('The raw, default, and effective Display Type settings.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('credentialPHID')
|
||||||
|
->setType('phid?')
|
||||||
|
->setDescription(
|
||||||
|
pht('The associated credential PHID, if one exists.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('disabled')
|
||||||
|
->setType('bool')
|
||||||
|
->setDescription(pht('True if the URI is disabled.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('builtin')
|
||||||
|
->setType('map<string, string>')
|
||||||
|
->setDescription(
|
||||||
|
pht('Information about builtin URIs.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('dateCreated')
|
||||||
|
->setType('int')
|
||||||
|
->setDescription(
|
||||||
|
pht('Epoch timestamp when the object was created.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('dateModified')
|
||||||
|
->setType('int')
|
||||||
|
->setDescription(
|
||||||
|
pht('Epoch timestamp when the object was last updated.')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFieldValuesForConduit() {
|
||||||
|
return array(
|
||||||
|
'repositoryPHID' => $this->getRepositoryPHID(),
|
||||||
|
'uri' => array(
|
||||||
|
'raw' => $this->getURI(),
|
||||||
|
'effective' => (string)$this->getDisplayURI(),
|
||||||
|
),
|
||||||
|
'io' => array(
|
||||||
|
'raw' => $this->getIOType(),
|
||||||
|
'default' => $this->getDefaultIOType(),
|
||||||
|
'effective' => $this->getEffectiveIOType(),
|
||||||
|
),
|
||||||
|
'display' => array(
|
||||||
|
'raw' => $this->getDisplayType(),
|
||||||
|
'default' => $this->getDefaultDisplayType(),
|
||||||
|
'effective' => $this->getEffectiveDisplayType(),
|
||||||
|
),
|
||||||
|
'credentialPHID' => $this->getCredentialPHID(),
|
||||||
|
'disabled' => (bool)$this->getIsDisabled(),
|
||||||
|
'builtin' => array(
|
||||||
|
'protocol' => $this->getBuiltinProtocol(),
|
||||||
|
'identifier' => $this->getBuiltinIdentifier(),
|
||||||
|
),
|
||||||
|
'dateCreated' => $this->getDateCreated(),
|
||||||
|
'dateModified' => $this->getDateModified(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConduitSearchAttachments() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorRepositoryURITransaction
|
||||||
|
extends PhabricatorApplicationTransaction {
|
||||||
|
|
||||||
|
const TYPE_REPOSITORY = 'diffusion.uri.repository';
|
||||||
|
const TYPE_URI = 'diffusion.uri.uri';
|
||||||
|
const TYPE_IO = 'diffusion.uri.io';
|
||||||
|
const TYPE_DISPLAY = 'diffusion.uri.display';
|
||||||
|
const TYPE_CREDENTIAL = 'diffusion.uri.credential';
|
||||||
|
const TYPE_DISABLE = 'diffusion.uri.disable';
|
||||||
|
|
||||||
|
public function getApplicationName() {
|
||||||
|
return 'repository';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionType() {
|
||||||
|
return PhabricatorRepositoryURIPHIDType::TYPECONST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
$author_phid = $this->getAuthorPHID();
|
||||||
|
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
|
switch ($this->getTransactionType()) {
|
||||||
|
case self::TYPE_URI:
|
||||||
|
return pht(
|
||||||
|
'%s changed this URI from "%s" to "%s".',
|
||||||
|
$this->renderHandleLink($author_phid),
|
||||||
|
$old,
|
||||||
|
$new);
|
||||||
|
case self::TYPE_IO:
|
||||||
|
$map = PhabricatorRepositoryURI::getIOTypeMap();
|
||||||
|
$old_label = idx(idx($map, $old, array()), 'label', $old);
|
||||||
|
$new_label = idx(idx($map, $new, array()), 'label', $new);
|
||||||
|
|
||||||
|
return pht(
|
||||||
|
'%s changed the display type for this URI from "%s" to "%s".',
|
||||||
|
$this->renderHandleLink($author_phid),
|
||||||
|
$old_label,
|
||||||
|
$new_label);
|
||||||
|
case self::TYPE_DISPLAY:
|
||||||
|
$map = PhabricatorRepositoryURI::getDisplayTypeMap();
|
||||||
|
$old_label = idx(idx($map, $old, array()), 'label', $old);
|
||||||
|
$new_label = idx(idx($map, $new, array()), 'label', $new);
|
||||||
|
|
||||||
|
return pht(
|
||||||
|
'%s changed the display type for this URI from "%s" to "%s".',
|
||||||
|
$this->renderHandleLink($author_phid),
|
||||||
|
$old_label,
|
||||||
|
$new_label);
|
||||||
|
case self::TYPE_DISABLE:
|
||||||
|
if ($new) {
|
||||||
|
return pht(
|
||||||
|
'%s disabled this URI.',
|
||||||
|
$this->renderHandleLink($author_phid));
|
||||||
|
} else {
|
||||||
|
return pht(
|
||||||
|
'%s enabled this URI.',
|
||||||
|
$this->renderHandleLink($author_phid));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
||||||
protected $devicePHID;
|
protected $devicePHID;
|
||||||
protected $repositoryVersion;
|
protected $repositoryVersion;
|
||||||
protected $isWriting;
|
protected $isWriting;
|
||||||
|
protected $lockOwner;
|
||||||
protected $writeProperties;
|
protected $writeProperties;
|
||||||
|
|
||||||
protected function getConfiguration() {
|
protected function getConfiguration() {
|
||||||
|
@ -16,6 +17,7 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
||||||
'repositoryVersion' => 'uint32',
|
'repositoryVersion' => 'uint32',
|
||||||
'isWriting' => 'bool',
|
'isWriting' => 'bool',
|
||||||
'writeProperties' => 'text?',
|
'writeProperties' => 'text?',
|
||||||
|
'lockOwner' => 'text255?',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_workingcopy' => array(
|
'key_workingcopy' => array(
|
||||||
|
@ -69,29 +71,33 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
||||||
* by default.
|
* by default.
|
||||||
*/
|
*/
|
||||||
public static function willWrite(
|
public static function willWrite(
|
||||||
|
AphrontDatabaseConnection $locked_connection,
|
||||||
$repository_phid,
|
$repository_phid,
|
||||||
$device_phid,
|
$device_phid,
|
||||||
array $write_properties) {
|
array $write_properties,
|
||||||
|
$lock_owner) {
|
||||||
|
|
||||||
$version = new self();
|
$version = new self();
|
||||||
$conn_w = $version->establishConnection('w');
|
|
||||||
$table = $version->getTableName();
|
$table = $version->getTableName();
|
||||||
|
|
||||||
queryfx(
|
queryfx(
|
||||||
$conn_w,
|
$locked_connection,
|
||||||
'INSERT INTO %T
|
'INSERT INTO %T
|
||||||
(repositoryPHID, devicePHID, repositoryVersion, isWriting,
|
(repositoryPHID, devicePHID, repositoryVersion, isWriting,
|
||||||
writeProperties)
|
writeProperties, lockOwner)
|
||||||
VALUES
|
VALUES
|
||||||
(%s, %s, %d, %d, %s)
|
(%s, %s, %d, %d, %s, %s)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
isWriting = VALUES(isWriting),
|
isWriting = VALUES(isWriting),
|
||||||
writeProperties = VALUES(writeProperties)',
|
writeProperties = VALUES(writeProperties),
|
||||||
|
lockOwner = VALUES(lockOwner)',
|
||||||
$table,
|
$table,
|
||||||
$repository_phid,
|
$repository_phid,
|
||||||
$device_phid,
|
$device_phid,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
phutil_json_encode($write_properties));
|
phutil_json_encode($write_properties),
|
||||||
|
$lock_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,7 +108,9 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
||||||
$repository_phid,
|
$repository_phid,
|
||||||
$device_phid,
|
$device_phid,
|
||||||
$old_version,
|
$old_version,
|
||||||
$new_version) {
|
$new_version,
|
||||||
|
$lock_owner) {
|
||||||
|
|
||||||
$version = new self();
|
$version = new self();
|
||||||
$conn_w = $version->establishConnection('w');
|
$conn_w = $version->establishConnection('w');
|
||||||
$table = $version->getTableName();
|
$table = $version->getTableName();
|
||||||
|
@ -111,17 +119,20 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
||||||
$conn_w,
|
$conn_w,
|
||||||
'UPDATE %T SET
|
'UPDATE %T SET
|
||||||
repositoryVersion = %d,
|
repositoryVersion = %d,
|
||||||
isWriting = 0
|
isWriting = 0,
|
||||||
|
lockOwner = NULL
|
||||||
WHERE
|
WHERE
|
||||||
repositoryPHID = %s AND
|
repositoryPHID = %s AND
|
||||||
devicePHID = %s AND
|
devicePHID = %s AND
|
||||||
repositoryVersion = %d AND
|
repositoryVersion = %d AND
|
||||||
isWriting = 1',
|
isWriting = 1 AND
|
||||||
|
lockOwner = %s',
|
||||||
$table,
|
$table,
|
||||||
$new_version,
|
$new_version,
|
||||||
$repository_phid,
|
$repository_phid,
|
||||||
$device_phid,
|
$device_phid,
|
||||||
$old_version);
|
$old_version,
|
||||||
|
$lock_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorSearchSchemaSpec
|
||||||
|
extends PhabricatorConfigSchemaSpec {
|
||||||
|
|
||||||
|
public function buildSchemata() {
|
||||||
|
$this->buildEdgeSchemata(new PhabricatorProfilePanelConfiguration());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1808,8 +1808,9 @@ abstract class PhabricatorEditEngine
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
throw new PhutilProxyException(
|
throw new PhutilProxyException(
|
||||||
pht(
|
pht(
|
||||||
'Exception when processing transaction of type "%s".',
|
'Exception when processing transaction of type "%s": %s',
|
||||||
$xaction['type']),
|
$xaction['type'],
|
||||||
|
$ex->getMessage()),
|
||||||
$ex);
|
$ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorBoolEditField
|
||||||
|
extends PhabricatorEditField {
|
||||||
|
|
||||||
|
private $options;
|
||||||
|
|
||||||
|
public function setOptions($off_label, $on_label) {
|
||||||
|
$this->options = array(
|
||||||
|
'0' => $off_label,
|
||||||
|
'1' => $on_label,
|
||||||
|
);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOptions() {
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newControl() {
|
||||||
|
$options = $this->getOptions();
|
||||||
|
|
||||||
|
if (!$options) {
|
||||||
|
$options = array(
|
||||||
|
'0' => pht('False'),
|
||||||
|
'1' => pht('True'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new AphrontFormSelectControl())
|
||||||
|
->setOptions($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newHTTPParameterType() {
|
||||||
|
return new AphrontBoolHTTPParameterType();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newConduitParameterType() {
|
||||||
|
return new ConduitBoolParameterType();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ final class PhabricatorTextAreaEditField
|
||||||
|
|
||||||
private $monospaced;
|
private $monospaced;
|
||||||
private $height;
|
private $height;
|
||||||
|
private $isStringList;
|
||||||
|
|
||||||
public function setMonospaced($monospaced) {
|
public function setMonospaced($monospaced) {
|
||||||
$this->monospaced = $monospaced;
|
$this->monospaced = $monospaced;
|
||||||
|
@ -24,6 +25,15 @@ final class PhabricatorTextAreaEditField
|
||||||
return $this->height;
|
return $this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setIsStringList($is_string_list) {
|
||||||
|
$this->isStringList = $is_string_list;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsStringList() {
|
||||||
|
return $this->isStringList;
|
||||||
|
}
|
||||||
|
|
||||||
protected function newControl() {
|
protected function newControl() {
|
||||||
$control = new AphrontFormTextAreaControl();
|
$control = new AphrontFormTextAreaControl();
|
||||||
|
|
||||||
|
@ -39,8 +49,25 @@ final class PhabricatorTextAreaEditField
|
||||||
return $control;
|
return $control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getValueForControl() {
|
||||||
|
$value = $this->getValue();
|
||||||
|
return implode("\n", $value);
|
||||||
|
}
|
||||||
|
|
||||||
protected function newConduitParameterType() {
|
protected function newConduitParameterType() {
|
||||||
return new ConduitStringParameterType();
|
if ($this->getIsStringList()) {
|
||||||
|
return new ConduitStringListParameterType();
|
||||||
|
} else {
|
||||||
|
return new ConduitStringParameterType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newHTTPParameterType() {
|
||||||
|
if ($this->getIsStringList()) {
|
||||||
|
return new AphrontStringListHTTPParameterType();
|
||||||
|
} else {
|
||||||
|
return new AphrontStringHTTPParameterType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
247
src/docs/user/cluster/cluster_devices.diviner
Normal file
247
src/docs/user/cluster/cluster_devices.diviner
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
@title Cluster: Devices
|
||||||
|
@group cluster
|
||||||
|
|
||||||
|
Guide to configuring hosts to act as cluster devices.
|
||||||
|
|
||||||
|
Cluster Context
|
||||||
|
===============
|
||||||
|
|
||||||
|
This document describes a step in configuring Phabricator to run on
|
||||||
|
multiple hosts in a cluster configuration. This is an advanced feature. For
|
||||||
|
more information on clustering, see @{article:Clustering Introduction}.
|
||||||
|
|
||||||
|
In this context, device configuration is mostly relevant to configuring
|
||||||
|
repository services in a cluster. You can find more details about this in
|
||||||
|
@{article:Cluster: Repositories}.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
Some cluster services need to be able to authenticate themselves and interact
|
||||||
|
with other services. For example, two repository hosts holding copies of the
|
||||||
|
same repository must be able to fetch changes from one another, even if the
|
||||||
|
repository is private.
|
||||||
|
|
||||||
|
Within a cluster, devices authenticate using SSH keys. Some operations happen
|
||||||
|
over SSH (using keys in a normal way, as you would when running `ssh` from the
|
||||||
|
command line), while others happen over HTTP (using SSH keys to sign requests).
|
||||||
|
|
||||||
|
Before hosts can authenticate to one another, you need to configure the
|
||||||
|
credentials so other devices know the keys can be trusted. Beyond establishing
|
||||||
|
trust, this configuration will establish //device identity//, so each host
|
||||||
|
knows which device it is explicitly.
|
||||||
|
|
||||||
|
Today, this is primarily necessary when configuring repository clusters.
|
||||||
|
|
||||||
|
|
||||||
|
Using Almanac
|
||||||
|
=============
|
||||||
|
|
||||||
|
The tool Phabricator uses to manage cluster devices is the **Almanac**
|
||||||
|
application, and most configuration will occur through the application's web
|
||||||
|
UI. If you are not familiar with it, see @{article:Almanac User Guide} first.
|
||||||
|
This document assumes you are familiar with Almanac concepts.
|
||||||
|
|
||||||
|
|
||||||
|
What Lies Ahead
|
||||||
|
===============
|
||||||
|
|
||||||
|
Here's a brief overview of the steps required to register cluster devices. The
|
||||||
|
remainder of this document walks through these points in more detail.
|
||||||
|
|
||||||
|
- Create an Almanac device record for each device.
|
||||||
|
- Generate, add, and trust SSH keys if necessary.
|
||||||
|
- Install Phabricator on the host.
|
||||||
|
- Use `bin/almanac register` from the host to register it as a device.
|
||||||
|
|
||||||
|
See below for guidance on each of these steps.
|
||||||
|
|
||||||
|
|
||||||
|
Individual vs Shared Keys
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Before getting started, you should choose how you plan to manage device SSH
|
||||||
|
keys. Trust and device identity are handled separately, and there are two ways
|
||||||
|
to set up SSH keys so that devices can authenticate with one another:
|
||||||
|
|
||||||
|
- you can generate a unique SSH key for each device; or
|
||||||
|
- you can generate one SSH key and share it across multiple devices.
|
||||||
|
|
||||||
|
Using **unique keys** allows the tools to do some more sanity/safety checks and
|
||||||
|
makes it a bit more difficult to misconfigure things, but you'll have to do
|
||||||
|
more work managing the actual keys. This may be a better choice if you are
|
||||||
|
setting up a small cluster (2-3 devices) for the first time.
|
||||||
|
|
||||||
|
Using **shared keys** makes key management easier but safety checks won't be
|
||||||
|
able to catch a few kinds of mistakes. This may be a better choice if you are
|
||||||
|
setting up a larger cluster, plan to expand the cluster later, or have
|
||||||
|
experience with Phabricator clustering.
|
||||||
|
|
||||||
|
Because all cluster keys are all-powerful, there is no material difference
|
||||||
|
between these methods from a security or trust viewpoint. Unique keys are just
|
||||||
|
potentially easier to administrate at small scales, while shared keys are
|
||||||
|
easier at larger scales.
|
||||||
|
|
||||||
|
|
||||||
|
Create Almanac Device Records
|
||||||
|
=============================
|
||||||
|
|
||||||
|
For each host you plan to make part of a Phabricator cluster, go to the
|
||||||
|
{nav Almanac} application and create a **device** record. For guidance on this
|
||||||
|
application, see @{article:Almanac User Guide}.
|
||||||
|
|
||||||
|
Add **interfaces** to each device record so Phabricator can tell how to
|
||||||
|
connect to these hosts. Normally, you'll add one HTTP interface (usually on
|
||||||
|
port 80) and one SSH interface (by default, on port 2222) to each device:
|
||||||
|
|
||||||
|
For example, if you are building a two-host repository cluster, you may end
|
||||||
|
up with records that look like these:
|
||||||
|
|
||||||
|
- Device: `repo001.mycompany.net`
|
||||||
|
- Interface: `123.0.0.1:2222`
|
||||||
|
- Interface: `123.0.0.1:80`
|
||||||
|
- Device: `repo002.mycopmany.net`
|
||||||
|
- Interface: `123.0.0.2:2222`
|
||||||
|
- Interface: `123.0.0.2:80`
|
||||||
|
|
||||||
|
Note that these hosts will normally run two `sshd` ports: the standard `sshd`
|
||||||
|
which you connect to to operate and administrate the host, and the special
|
||||||
|
Phabricator `sshd` that you connect to to clone and push repositories.
|
||||||
|
|
||||||
|
You should specify the Phabricator `sshd` port, **not** the standard `sshd`
|
||||||
|
port.
|
||||||
|
|
||||||
|
If you're using **unique** SSH keys for each device, continue to the next step.
|
||||||
|
|
||||||
|
If you're using **shared** SSH keys, create a third device with no interfaces,
|
||||||
|
like `keywarden.mycompany.net`. This device will just be used as a container to
|
||||||
|
hold the trusted SSH key and is not a real device.
|
||||||
|
|
||||||
|
NOTE: Do **not** create a **service** record yet. Today, service records become
|
||||||
|
active immediately once they are created, and you haven't set things up yet.
|
||||||
|
|
||||||
|
|
||||||
|
Generate and Trust SSH Keys
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Next, you need to generate or upload SSH keys and mark them as trusted. Marking
|
||||||
|
a key as trusted gives it tremendous power.
|
||||||
|
|
||||||
|
If you're using **unique** SSH keys, upload or generate a key for each
|
||||||
|
individual device from the device detail screen in the Almanac web UI. Save the
|
||||||
|
private keys for the next step.
|
||||||
|
|
||||||
|
If you're using a **shared** SSH key, upload or generate a single key for
|
||||||
|
the keywarden device from the device detail screen in the Almanac web UI.
|
||||||
|
Save the private key for the next step.
|
||||||
|
|
||||||
|
Regardless of how many keys you generated, take the key IDs from the tables
|
||||||
|
in the web UI and run this command from the command line for each key, to mark
|
||||||
|
each key as trusted:
|
||||||
|
|
||||||
|
```
|
||||||
|
phabricator/ $ ./bin/almanac trust-key --id <key-id-1>
|
||||||
|
phabricator/ $ ./bin/almanac trust-key --id <key-id-2>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The warnings this command emits are serious. The private keys are now trusted,
|
||||||
|
and allow any user or device possessing them to sign requests that bypass
|
||||||
|
policy checks without requiring additional credentials. Guard them carefully!
|
||||||
|
|
||||||
|
If you need to revoke trust for a key later, use `untrust-key`:
|
||||||
|
|
||||||
|
```
|
||||||
|
phabricator/ $ ./bin/almanac untrust-key --id <key-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the keys are trusted, continue to the next step.
|
||||||
|
|
||||||
|
|
||||||
|
Install Phabricator
|
||||||
|
===================
|
||||||
|
|
||||||
|
If you haven't already, install Phabricator on each device you plan to enroll
|
||||||
|
in the cluster. Cluster repository devices must provide services over both HTTP
|
||||||
|
and SSH, so you need to install and configure both a webserver and a
|
||||||
|
Phabricator `sshd` on these hosts.
|
||||||
|
|
||||||
|
Generally, you will follow whatever process you otherwise use when installing
|
||||||
|
Phabricator.
|
||||||
|
|
||||||
|
NOTE: Do not start the daemons on the new devices yet. They won't work properly
|
||||||
|
until you've finished configuring things.
|
||||||
|
|
||||||
|
Once Phabricator is installed, you can enroll the devices in the cluster by
|
||||||
|
registering them.
|
||||||
|
|
||||||
|
|
||||||
|
Register Devices
|
||||||
|
================
|
||||||
|
|
||||||
|
To register a host as an Almanac device, use `bin/almanac register`.
|
||||||
|
|
||||||
|
If you are using **unique** keys, run it like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register \
|
||||||
|
--device <device> \
|
||||||
|
--private-key <key>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, you might run this command on `repo001` when using unique keys:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register \
|
||||||
|
--device repo001.mycompany.net \
|
||||||
|
--private-key /path/to/private.key
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using a **shared** key, this will be a little more complicated
|
||||||
|
because you need to override some checks that are intended to prevent mistakes.
|
||||||
|
Use the `--identify-as` flag to choose a device identity:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register \
|
||||||
|
--device <keywarden-device> \
|
||||||
|
--private-key <key> \
|
||||||
|
--identify-as <actual-device>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, you might run this command on `repo001` when using a shared key:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/almanac register
|
||||||
|
--device keywarden.mycompany.net \
|
||||||
|
--private-key /path/to/private-key \
|
||||||
|
--identify-as repo001.mycompany.net
|
||||||
|
```
|
||||||
|
|
||||||
|
In particular, note that `--device` is always the **trusted** device associated
|
||||||
|
with the trusted key. The `--identify-as` flag allows several different hosts
|
||||||
|
to share the same key but still identify as different devices.
|
||||||
|
|
||||||
|
The overall effect of the `bin/almanac` command is to copy identity and key
|
||||||
|
files into `phabricator/conf/keys/`. You can inspect the results by examining
|
||||||
|
that directory. The helper script just catches potential mistakes and makes
|
||||||
|
sure the process is completed correctly.
|
||||||
|
|
||||||
|
Note that a copy of the active private key is stored in the `conf/keys/`
|
||||||
|
directory permanently.
|
||||||
|
|
||||||
|
When converting a host into a cluster host, you may need to revisit
|
||||||
|
@{article:Diffusion User Guide: Repository Hosting} and double check the `sudo`
|
||||||
|
permission for the host. In particular, cluster hosts need to be able to run
|
||||||
|
`ssh` via `sudo` so they can read the device private key.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Now that devices are registered, you can build cluster services from them.
|
||||||
|
Return to the relevant cluster service documentation to continue:
|
||||||
|
|
||||||
|
- build repository clusters with @{article:Cluster: Repositories};
|
||||||
|
- return to @{article:Clustering Introduction}; or
|
||||||
|
- review the Almanac application with @{article:Almanac User Guide}.
|
|
@ -9,9 +9,9 @@ Overview
|
||||||
WARNING: This feature is a very early prototype; the features this document
|
WARNING: This feature is a very early prototype; the features this document
|
||||||
describes are mostly speculative fantasy.
|
describes are mostly speculative fantasy.
|
||||||
|
|
||||||
If you use Git or Mercurial, you can deploy Phabricator with multiple
|
If you use Git, you can deploy Phabricator with multiple repository hosts,
|
||||||
repository hosts, configured so that each host is readable and writable. The
|
configured so that each host is readable and writable. The advantages of doing
|
||||||
advantages of doing this are:
|
this are:
|
||||||
|
|
||||||
- you can completely survive the loss of repository hosts;
|
- you can completely survive the loss of repository hosts;
|
||||||
- reads and writes can scale across multiple machines; and
|
- reads and writes can scale across multiple machines; and
|
||||||
|
@ -22,24 +22,6 @@ This configuration is complex, and many installs do not need to pursue it.
|
||||||
This configuration is not currently supported with Subversion or Mercurial.
|
This configuration is not currently supported with Subversion or Mercurial.
|
||||||
|
|
||||||
|
|
||||||
Repository Hosts
|
|
||||||
================
|
|
||||||
|
|
||||||
Repository hosts must run a complete, fully configured copy of Phabricator,
|
|
||||||
including a webserver. They must also run a properly configured `sshd`.
|
|
||||||
|
|
||||||
Generally, these hosts will run the same set of services and configuration that
|
|
||||||
web hosts run. If you prefer, you can overlay these services and put web and
|
|
||||||
repository services on the same hosts. See @{article:Clustering Introduction}
|
|
||||||
for some guidance on overlaying services.
|
|
||||||
|
|
||||||
When a user requests information about a repository that can only be satisfied
|
|
||||||
by examining a repository working copy, the webserver receiving the request
|
|
||||||
will make an HTTP service call to a repository server which hosts the
|
|
||||||
repository to retrieve the data it needs. It will use the result of this query
|
|
||||||
to respond to the user.
|
|
||||||
|
|
||||||
|
|
||||||
How Reads and Writes Work
|
How Reads and Writes Work
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
@ -95,6 +77,162 @@ Other mitigations are possible, but securing a network against the NSA and
|
||||||
similar agents of other rogue nations is beyond the scope of this document.
|
similar agents of other rogue nations is beyond the scope of this document.
|
||||||
|
|
||||||
|
|
||||||
|
Repository Hosts
|
||||||
|
================
|
||||||
|
|
||||||
|
Repository hosts must run a complete, fully configured copy of Phabricator,
|
||||||
|
including a webserver. They must also run a properly configured `sshd`.
|
||||||
|
|
||||||
|
If you are converting existing hosts into cluster hosts, you may need to
|
||||||
|
revisit @{article:Diffusion User Guide: Repository Hosting} and make sure
|
||||||
|
the system user accounts have all the necessary `sudo` permissions. In
|
||||||
|
particular, cluster devices need `sudo` access to `ssh` so they can read
|
||||||
|
device keys.
|
||||||
|
|
||||||
|
Generally, these hosts will run the same set of services and configuration that
|
||||||
|
web hosts run. If you prefer, you can overlay these services and put web and
|
||||||
|
repository services on the same hosts. See @{article:Clustering Introduction}
|
||||||
|
for some guidance on overlaying services.
|
||||||
|
|
||||||
|
When a user requests information about a repository that can only be satisfied
|
||||||
|
by examining a repository working copy, the webserver receiving the request
|
||||||
|
will make an HTTP service call to a repository server which hosts the
|
||||||
|
repository to retrieve the data it needs. It will use the result of this query
|
||||||
|
to respond to the user.
|
||||||
|
|
||||||
|
|
||||||
|
Setting up a Cluster Services
|
||||||
|
=============================
|
||||||
|
|
||||||
|
To set up clustering, first register the devices that you want to use as part
|
||||||
|
of the cluster with Almanac. For details, see @{article:Cluster: Devices}.
|
||||||
|
|
||||||
|
NOTE: Once you create a service, new repositories will immediately allocate
|
||||||
|
on it. You may want to disable repository creation during initial setup.
|
||||||
|
|
||||||
|
Once the hosts are registered as devices, you can create a new service in
|
||||||
|
Almanac:
|
||||||
|
|
||||||
|
- First, register at least one device according to the device clustering
|
||||||
|
instructions.
|
||||||
|
- Create a new service of type **Phabricator Cluster: Repository** in
|
||||||
|
Almanac.
|
||||||
|
- Bind this service to all the interfaces on the device or devices.
|
||||||
|
- For each binding, add a `protocol` key with one of these values:
|
||||||
|
`ssh`, `http`, `https`.
|
||||||
|
|
||||||
|
For example, a service might look like this:
|
||||||
|
|
||||||
|
- Service: `repos001.mycompany.net`
|
||||||
|
- Binding: `repo001.mycompany.net:80`, `protocol=http`
|
||||||
|
- Binding: `repo001.mycompany.net:2222`, `protocol=ssh`
|
||||||
|
|
||||||
|
The service itself has a `closed` property. You can set this to `true` to
|
||||||
|
disable new repository allocations on this service (for example, if it is
|
||||||
|
reaching capacity).
|
||||||
|
|
||||||
|
|
||||||
|
Migrating to Clustered Services
|
||||||
|
===============================
|
||||||
|
|
||||||
|
To convert existing repositories on an install into cluster repositories, you
|
||||||
|
will generally perform these steps:
|
||||||
|
|
||||||
|
- Register the existing host as a cluster device.
|
||||||
|
- Configure a single host repository service using //only// that host.
|
||||||
|
|
||||||
|
This puts you in a transitional state where repositories on the host can work
|
||||||
|
as either on-host repositories or cluster repositories. You can move forward
|
||||||
|
from here slowly and make sure services still work, with a quick path back to
|
||||||
|
safety if you run into trouble.
|
||||||
|
|
||||||
|
To move forward, migrate one repository to the service and make sure things
|
||||||
|
work correctly. If you run into issues, you can back out by migrating the
|
||||||
|
repository off the service.
|
||||||
|
|
||||||
|
To migrate a repository onto a cluster service, use this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/repository clusterize <repository> --service <service>
|
||||||
|
```
|
||||||
|
|
||||||
|
To migrate a repository back off a service, use this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/repoistory clusterize <repository> --remove-service
|
||||||
|
```
|
||||||
|
|
||||||
|
This command only changes how Phabricator connects to the repository; it does
|
||||||
|
not move any data or make any complex structural changes.
|
||||||
|
|
||||||
|
When Phabricator needs information about a non-clustered repository, it just
|
||||||
|
runs a command like `git log` directly on disk. When Phabricator needs
|
||||||
|
information about a clustered repository, it instead makes a service call to
|
||||||
|
another server, asking that server to run `git log` instead.
|
||||||
|
|
||||||
|
In a single-host cluster the server will make this service call to itself, so
|
||||||
|
nothing will really change. But this //is// an effective test for most
|
||||||
|
possible configuration mistakes.
|
||||||
|
|
||||||
|
If your canary repository works well, you can migrate the rest of your
|
||||||
|
repositories when ready (you can use `bin/repository list` to quickly get a
|
||||||
|
list of all repository monograms).
|
||||||
|
|
||||||
|
Once all repositories are migrated, you've reached a stable state and can
|
||||||
|
remain here as long as you want. This state is sufficient to convert daemons,
|
||||||
|
SSH, and web services into clustered versions and spread them across multiple
|
||||||
|
machines if those goals are more interesting.
|
||||||
|
|
||||||
|
Obviously, your single-device "cluster" will not be able to survive the loss of
|
||||||
|
the single repository host, but you can take as long as you want to expand the
|
||||||
|
cluster and add redundancy.
|
||||||
|
|
||||||
|
After creating a service, you do not need to `clusterize` new repositories:
|
||||||
|
they will automatically allocate onto an open service.
|
||||||
|
|
||||||
|
When you're ready to expand the cluster, continue below.
|
||||||
|
|
||||||
|
|
||||||
|
Expanding a Cluster
|
||||||
|
===================
|
||||||
|
|
||||||
|
To expand an existing cluster, follow these general steps:
|
||||||
|
|
||||||
|
- Register new devices in Almanac.
|
||||||
|
- Add bindings to the new devices to the repository service, also in Almanac.
|
||||||
|
- Start the daemons on the new devices.
|
||||||
|
|
||||||
|
For instructions on configuring and registering devices, see
|
||||||
|
@{article:Cluster: Devices}.
|
||||||
|
|
||||||
|
As soon as you add active bindings to a service, Phabricator will begin
|
||||||
|
synchronizing repositories and sending traffic to the new device. You do not
|
||||||
|
need to copy any repository data to the device: Phabricator will automatically
|
||||||
|
synchronize it.
|
||||||
|
|
||||||
|
If you have a large amount of repository data, you may want to help this
|
||||||
|
process along by copying the repository directory from an existing cluster
|
||||||
|
device before bringing the new host online. This is optional, but can reduce
|
||||||
|
the amount of time required to fully synchronize the cluster.
|
||||||
|
|
||||||
|
You do not need to synchronize the most up-to-date data or stop writes during
|
||||||
|
this process. For example, loading the most recent backup snapshot onto the new
|
||||||
|
device will substantially reduce the amount of data that needs to be
|
||||||
|
synchronized.
|
||||||
|
|
||||||
|
|
||||||
|
Contracting a Cluster
|
||||||
|
=====================
|
||||||
|
|
||||||
|
To reduce the size of an existing cluster, follow these general steps:
|
||||||
|
|
||||||
|
- Disable the bindings from the service to the dead device in Almanac.
|
||||||
|
|
||||||
|
If you are removing a device because it failed abruptly (or removing several
|
||||||
|
devices at once) it is possible that some repositories will have lost all their
|
||||||
|
leaders. See "Loss of Leaders" below to understand and resolve this.
|
||||||
|
|
||||||
|
|
||||||
Monitoring Services
|
Monitoring Services
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
|
@ -273,7 +273,7 @@ You do not need to adjust the `"admin"` server.
|
||||||
**Aphlict**: Your Aphlict configuration should make these adjustments to
|
**Aphlict**: Your Aphlict configuration should make these adjustments to
|
||||||
the `"client"` server:
|
the `"client"` server:
|
||||||
|
|
||||||
- The `protocol` should be `"http"`: `nginx` will send plain HTTP traffic
|
- Do not specify any `ssl.*` options: `nginx` will send plain HTTP traffic
|
||||||
to Aphlict.
|
to Aphlict.
|
||||||
- Optionally, you can `listen` on `127.0.0.1` instead of `0.0.0.0`, because
|
- Optionally, you can `listen` on `127.0.0.1` instead of `0.0.0.0`, because
|
||||||
the server will no longer receive external traffic.
|
the server will no longer receive external traffic.
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
|
|
||||||
Guide to configuring Phabricator repository hosting.
|
Guide to configuring Phabricator repository hosting.
|
||||||
|
|
||||||
= Overview =
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
Phabricator can host repositories and provide authenticated read and write
|
Phabricator can host repositories and provide authenticated read and write
|
||||||
access to them over HTTP and SSH. This document describes how to configure
|
access to them over HTTP and SSH. This document describes how to configure
|
||||||
repository hosting.
|
repository hosting.
|
||||||
|
|
||||||
= Understanding Supported Protocols =
|
Understanding Supported Protocols
|
||||||
|
=================================
|
||||||
|
|
||||||
Phabricator supports hosting over these protocols:
|
Phabricator supports hosting over these protocols:
|
||||||
|
|
||||||
|
@ -35,99 +37,165 @@ performant, but HTTP is easier to set up and supports anonymous access.
|
||||||
| Performance | Better | Okay |
|
| Performance | Better | Okay |
|
||||||
| Setup | Hard | Easy |
|
| Setup | Hard | Easy |
|
||||||
|
|
||||||
Each repository can be configured individually, and you can use either protocol,
|
Each repository can be configured individually, and you can use either
|
||||||
or both, or a mixture across different repositories.
|
protocol, or both, or a mixture across different repositories.
|
||||||
|
|
||||||
SSH is recommended unless you need anonymous access, or are not able to
|
SSH is recommended unless you need anonymous access, or are not able to
|
||||||
configure it for technical reasons.
|
configure it for technical reasons.
|
||||||
|
|
||||||
= Configuring System User Accounts =
|
|
||||||
|
|
||||||
Phabricator uses as many as three user accounts. This section will guide you
|
Creating System User Accounts
|
||||||
through creating and configuring them. These are system user accounts on the
|
=============================
|
||||||
machine Phabricator runs on, not Phabricator user accounts.
|
|
||||||
|
|
||||||
The system accounts are:
|
Phabricator uses two system user accounts, plus a third account if you
|
||||||
|
configure SSH access. This section will guide you through creating and
|
||||||
|
configuring them. These are system user accounts on the machine Phabricator
|
||||||
|
runs on, not Phabricator user accounts.
|
||||||
|
|
||||||
- The user the daemons run as. We'll call this `daemon-user`. For more
|
The system accounts Phabricator uses are:
|
||||||
information on the daemons, see @{article:Managing Daemons with phd}. This
|
|
||||||
|
- The user the webserver runs as. We'll call this `www-user`.
|
||||||
|
- The user the daemons run as. We'll call this `daemon-user`. This
|
||||||
user is the only user which will interact with the repositories directly.
|
user is the only user which will interact with the repositories directly.
|
||||||
Other accounts will `sudo` to this account in order to perform VCS
|
Other accounts will `sudo` to this account in order to perform repository
|
||||||
operations.
|
operations.
|
||||||
- The user the webserver runs as. We'll call this `www-user`. If you do not
|
- The user that humans will connect over SSH as. We'll call this `vcs-user`.
|
||||||
plan to make repositories available over HTTP, you do not need to perform
|
|
||||||
any special configuration for this user.
|
|
||||||
- The user that users will connect over SSH as. We'll call this `vcs-user`.
|
|
||||||
If you do not plan to make repositories available over SSH, you do not need
|
If you do not plan to make repositories available over SSH, you do not need
|
||||||
to perform any special configuration for this user.
|
to create or configure this user.
|
||||||
|
|
||||||
To configure these users:
|
To create these users:
|
||||||
|
|
||||||
|
- Create a `www-user` if one does not already exist. In most cases, this
|
||||||
|
user will already exist and you just need to identify which user it is. Run
|
||||||
|
your webserver as this user.
|
||||||
- Create a `daemon-user` if one does not already exist (you can call this user
|
- Create a `daemon-user` if one does not already exist (you can call this user
|
||||||
whatever you want, or use an existing account). When you start the daemons,
|
whatever you want, or use an existing account). Below, you'll configure
|
||||||
start them using this user.
|
the daemons to start as this user.
|
||||||
- Create a `www-user` if one does not already exist. Run your webserver as
|
- Create a `vcs-user` if one does not already exist and you plan to set up
|
||||||
this user. In most cases, this user will already exist.
|
SSH. When users clone repositories, they will use a URI like
|
||||||
- Create a `vcs-user` if one does not already exist. Common names for this
|
`vcs-user@phabricator.yourcompany.com`, so common names for this user are
|
||||||
user are `git` or `hg`. When users clone repositories, they will use a URI
|
`git` or `hg`.
|
||||||
like `vcs-user@phabricator.yourcompany.com`.
|
|
||||||
|
|
||||||
Now, allow the `vcs-user` and `www-user` to `sudo` as the `daemon-user`. Add
|
Continue below to configure these accounts.
|
||||||
this to `/etc/sudoers`, using `visudo` or `sudoedit`.
|
|
||||||
|
|
||||||
If you plan to use SSH:
|
|
||||||
|
|
||||||
vcs-user ALL=(daemon-user) SETENV: NOPASSWD: /path/to/bin/git-upload-pack, /path/to/bin/git-receive-pack, /path/to/bin/hg, /path/to/bin/svnserve
|
Configuring Phabricator
|
||||||
|
=======================
|
||||||
|
|
||||||
If you plan to use HTTP:
|
Now that you have created or identified these accounts, update the Phabricator
|
||||||
|
configuration to specify them.
|
||||||
|
|
||||||
www-user ALL=(daemon-user) SETENV: NOPASSWD: /usr/bin/git-http-backend, /usr/bin/hg
|
First, set `phd.user` to the `daemon-user`:
|
||||||
|
|
||||||
Replace `vcs-user`, `www-user` and `daemon-user` with the right usernames for
|
```
|
||||||
your configuration. Make sure all the paths point to the real locations of the
|
phabricator/ $ ./bin/config set phd.user daemon-user
|
||||||
binaries on your system. You can omit any binaries associated with VCSes you do
|
```
|
||||||
not use.
|
|
||||||
|
|
||||||
Adding these commands to `sudoers` will allow the daemon and webserver users to
|
Restart the daemons to make sure this configuration works properly. They should
|
||||||
write to repositories as the daemon user.
|
start as the correct user automatically.
|
||||||
|
|
||||||
Before saving and closing `/etc/sudoers`, look for this line:
|
If you're using a `vcs-user` for SSH, you should also configure that:
|
||||||
|
|
||||||
|
```
|
||||||
|
phabricator/ $ ./bin/config set diffusion.ssh-user vcs-user
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, you'll set up `sudo` permissions so these users can interact with one
|
||||||
|
another.
|
||||||
|
|
||||||
|
|
||||||
|
Configuring Sudo
|
||||||
|
================
|
||||||
|
|
||||||
|
The `www-user` and `vcs-user` need to be able to `sudo` as the `daemon-user`
|
||||||
|
so they can interact with repositories.
|
||||||
|
|
||||||
|
To grant them access, edit the `sudo` system configuration. On many systems,
|
||||||
|
you will do this by modifying the `/etc/sudoers` file using `visudo` or
|
||||||
|
`sudoedit`. In some cases, you may add a new file to `/etc/sudoers.d` instead.
|
||||||
|
|
||||||
|
To give a user account `sudo` access to run a list of binaries, add a line like
|
||||||
|
this to the configuration file (this example would grant `vcs-user` permission
|
||||||
|
to run `ls` as `daemon-user`):
|
||||||
|
|
||||||
|
```
|
||||||
|
vcs-user ALL=(daemon-user) SETENV: NOPASSWD: /path/to/bin/ls
|
||||||
|
```
|
||||||
|
|
||||||
|
The `www-user` needs to be able to run these binaries as the `daemon-user`:
|
||||||
|
|
||||||
|
- `git` (if using Git)
|
||||||
|
- `git-http-backend` (if using Git)
|
||||||
|
- `hg` (if using Mercurial)
|
||||||
|
- `ssh` (if configuring clusters)
|
||||||
|
|
||||||
|
If you plan to use SSH, the `vcs-user` needs to be able to run these binaries
|
||||||
|
as the `daemon-user`:
|
||||||
|
|
||||||
|
- `git` (if using Git)
|
||||||
|
- `git-upload-pack` (if using Git)
|
||||||
|
- `git-receive-pack` (if using Git)
|
||||||
|
- `hg` (if using Mercurial)
|
||||||
|
- `svnserve` (if using Subversion)
|
||||||
|
- `ssh` (if configuring clusters)
|
||||||
|
|
||||||
|
Identify the full paths to all of these binaries on your system and add the
|
||||||
|
appropriate permissions to the `sudo` configuration.
|
||||||
|
|
||||||
|
Normally, you'll add two lines that look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
www-user ALL=(daemon-user) SETENV: NOPASSWD: /path/to/x, /path/to/y, ...
|
||||||
|
vcs-user ALL=(daemon-user) SETENV: NOPASSWD: /path/to/x, /path/to/y, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This is just a template. In the real configuration file, you need to:
|
||||||
|
|
||||||
|
- Replace `www-user`, `dameon-user` and `vcs-user` with the correct
|
||||||
|
usernames for your system.
|
||||||
|
- List every binary that these users need access to, as described above.
|
||||||
|
- Make sure each binary path is the full path to the correct binary location
|
||||||
|
on your system.
|
||||||
|
|
||||||
|
Before continuing, look for this line in your `sudo` configuration:
|
||||||
|
|
||||||
Defaults requiretty
|
Defaults requiretty
|
||||||
|
|
||||||
If it's present, comment it out by putting a `#` at the beginning of the line.
|
If it's present, comment it out by putting a `#` at the beginning of the line.
|
||||||
With this option enabled, VCS SSH sessions won't be able to use `sudo`.
|
With this option enabled, VCS SSH sessions won't be able to use `sudo`.
|
||||||
|
|
||||||
|
|
||||||
|
Additional SSH User Configuration
|
||||||
|
=================================
|
||||||
|
|
||||||
If you're planning to use SSH, you should also edit `/etc/passwd` and
|
If you're planning to use SSH, you should also edit `/etc/passwd` and
|
||||||
`/etc/shadow` to make sure the `vcs-user` account is set up correctly.
|
`/etc/shadow` to make sure the `vcs-user` account is set up correctly.
|
||||||
|
|
||||||
- Open `/etc/shadow` and find the line for the `vcs-user` account.
|
**`/etc/shadow`**: Open `/etc/shadow` and find the line for the `vcs-user`
|
||||||
- The second field (which is the password field) must not be set to
|
account.
|
||||||
`!!`. This value will prevent login. If it is set to `!!`, edit it
|
|
||||||
and set it to `NP` ("no password") instead.
|
|
||||||
- Open `/etc/passwd` and find the line for the `vcs-user` account.
|
|
||||||
- The last field (which is the login shell) must be set to a real shell.
|
|
||||||
If it is set to something like `/bin/false`, then `sshd` will not be able
|
|
||||||
to execute commands. Instead, you should set it to a real shell, like
|
|
||||||
`/bin/sh`.
|
|
||||||
|
|
||||||
Finally, once you've configured `/etc/sudoers`, `/etc/shadow` and `/etc/passwd`,
|
The second field (which is the password field) must not be set to `!!`. This
|
||||||
set `phd.user` to the `daemon-user`:
|
value will prevent login. If it is set to `!!`, edit it and set it to `NP` ("no
|
||||||
|
password") instead.
|
||||||
|
|
||||||
phabricator/ $ ./bin/config set phd.user daemon-user
|
**`/etc/passwd`**: Open `/etc/passwd` and find the line for the `vcs-user`
|
||||||
|
account.
|
||||||
|
|
||||||
If you're using a `vcs-user`, you should also configure that here:
|
The last field (which is the login shell) must be set to a real shell. If it is
|
||||||
|
set to something like `/bin/false`, then `sshd` will not be able to execute
|
||||||
|
commands. Instead, you should set it to a real shell, like `/bin/sh`.
|
||||||
|
|
||||||
phabricator/ $ ./bin/config set diffusion.ssh-user vcs-user
|
|
||||||
|
|
||||||
= Configuring HTTP =
|
Configuring HTTP
|
||||||
|
================
|
||||||
|
|
||||||
If you plan to use authenticated HTTP, you need to set
|
If you plan to serve repositories over authenticated HTTP, you need to set
|
||||||
`diffusion.allow-http-auth` in Config. If you don't plan to use HTTP, or plan to
|
`diffusion.allow-http-auth` in Config. If you don't plan to serve repositories
|
||||||
use only anonymous HTTP, you can leave this setting disabled.
|
over HTTP (or plan to use only anonymous HTTP) you can leave this setting
|
||||||
|
disabled.
|
||||||
|
|
||||||
If you plan to use authenticated HTTP, you'll also need to configure a VCS
|
If you plan to use authenticated HTTP, you (and all other users) also need to
|
||||||
password in {nav Settings > VCS Password}.
|
configure a VCS password for your account in {nav Settings > VCS Password}.
|
||||||
|
|
||||||
Your VCS password must be a different password than your main Phabricator
|
Your VCS password must be a different password than your main Phabricator
|
||||||
password because VCS passwords are very easy to accidentally disclose. They are
|
password because VCS passwords are very easy to accidentally disclose. They are
|
||||||
|
@ -136,60 +204,58 @@ and present in command output and logs. We strongly encourage you to use SSH
|
||||||
instead of HTTP to authenticate access to repositories.
|
instead of HTTP to authenticate access to repositories.
|
||||||
|
|
||||||
Otherwise, if you've configured system accounts above, you're all set. No
|
Otherwise, if you've configured system accounts above, you're all set. No
|
||||||
additional server configuration is required to make HTTP work.
|
additional server configuration is required to make HTTP work. You should now
|
||||||
|
be able to fetch and push repositories over HTTP. See "Cloning a Repository"
|
||||||
|
below for more details.
|
||||||
|
|
||||||
= Configuring SSH =
|
If you're having trouble, see "Troubleshooting HTTP" below.
|
||||||
|
|
||||||
SSH access requires some additional setup. Here's an overview of how setup
|
|
||||||
works:
|
|
||||||
|
|
||||||
- You'll move the normal `sshd` daemon to another port, like `222`. When
|
Configuring SSH
|
||||||
connecting to the machine to administrate it, you'll use this alternate
|
===============
|
||||||
port to get a normal login shell.
|
|
||||||
- You'll run a highly restricted `sshd` on port 22, with a special locked-down
|
|
||||||
configuration that uses Phabricator to authorize users and execute commands.
|
|
||||||
- The `sshd` on port 22 **MUST** be 6.2 or newer, because Phabricator relies
|
|
||||||
on the `AuthorizedKeysCommand` option.
|
|
||||||
|
|
||||||
Here's a walkthrough of how to perform this configuration in detail:
|
SSH access requires some additional setup. You will configure and run a second,
|
||||||
|
restricted copy of `sshd` on the machine, on a different port from the standard
|
||||||
|
`sshd`. This special copy of `sshd` will serve repository requests and provide
|
||||||
|
other Phabricator SSH services.
|
||||||
|
|
||||||
**Move Normal SSHD**: Be careful when editing the configuration for `sshd`. If
|
NOTE: The Phabricator `sshd` service **MUST** be 6.2 or newer, because
|
||||||
you get it wrong, you may lock yourself out of the machine. Restarting `sshd`
|
Phabricator relies on the `AuthorizedKeysCommand` option.
|
||||||
generally will not interrupt existing connections, but you should exercise
|
|
||||||
caution. Two strategies you can use to mitigate this risk are: smoke-test
|
|
||||||
configuration by starting a second `sshd`; and use a `screen` session which
|
|
||||||
automatically repairs configuration unless stopped.
|
|
||||||
|
|
||||||
To smoke-test a configuration, just start another `sshd` using the `-f` flag:
|
**Choose a Port**: These instructions will configure the alternate `sshd` on
|
||||||
|
port `2222`. This is easy to configure, but if you run the service on this port
|
||||||
|
users will clone and push to URIs like `ssh://git@host.com:2222/`, which is
|
||||||
|
a little ugly.
|
||||||
|
|
||||||
sudo /path/to/sshd -f /path/to/config_file.edited
|
The easiest way to fix this is to put a load balancer in front of the host and
|
||||||
|
have it forward TCP traffic on port `22` to port `2222`. Then users can clone
|
||||||
|
from `ssh://git@host.com/` without an explicit port number and you don't need
|
||||||
|
to do anything else.
|
||||||
|
|
||||||
You can then connect and make sure the edited config file is valid before
|
Alternatively, you can move the administrative `sshd` to a new port, then run
|
||||||
replacing your primary configuration file.
|
Phabricator `sshd` on port 22. This is complicated and risky. See "Moving the
|
||||||
|
sshd Port" below for help.
|
||||||
|
|
||||||
To automatically repair configuration, start a `screen` session with a command
|
Finally, you can just run on port `2222` and accept the explicit port in the
|
||||||
like this in it:
|
URIs. This is the simplest approach, and you can start here and clean things
|
||||||
|
up later.
|
||||||
|
|
||||||
sleep 60 ; mv sshd_config.good sshd_config ; /etc/init.d/sshd restart
|
If you plan to connect to a port other than `22`, you should set this port
|
||||||
|
as `diffusion.ssh-port` in your Phabricator config:
|
||||||
|
|
||||||
The specific command may vary for your system, but the general idea is to have
|
```
|
||||||
the machine automatically restore configuration after some period of time if
|
$ ./bin/config set diffusion.ssh-port 2222
|
||||||
you don't stop it. If you lock yourself out, this will fix things automatically.
|
```
|
||||||
|
|
||||||
Now that you're ready to edit your configuration, open up your `sshd` config
|
This port is not special, and you are free to choose a different port, provided
|
||||||
(often `/etc/ssh/sshd_config`) and change the `Port` setting to some other port,
|
you make the appropriate configuration adjustment below.
|
||||||
like `222` (you can choose any port other than 22).
|
|
||||||
|
|
||||||
Port 222
|
**Configure and Start Phabricator SSHD**: Now, you'll configure and start a
|
||||||
|
copy of `sshd` which will serve Phabricator services, including repositories,
|
||||||
|
over SSH.
|
||||||
|
|
||||||
Very carefully, restart `sshd`. Verify that you can connect on the new port:
|
This instance will use a special locked-down configuration that uses
|
||||||
|
Phabricator to handle authentication and command execution.
|
||||||
ssh -p 222 ...
|
|
||||||
|
|
||||||
**Configure and Start Phabricator SSHD**: Now, configure and start a second
|
|
||||||
`sshd` instance which will run on port `22`. This instance will use a special
|
|
||||||
locked-down configuration that uses Phabricator to handle authentication and
|
|
||||||
command execution.
|
|
||||||
|
|
||||||
There are three major steps:
|
There are three major steps:
|
||||||
|
|
||||||
|
@ -200,10 +266,16 @@ There are three major steps:
|
||||||
**Create `phabricator-ssh-hook.sh`**: Copy the template in
|
**Create `phabricator-ssh-hook.sh`**: Copy the template in
|
||||||
`phabricator/resources/sshd/phabricator-ssh-hook.sh` to somewhere like
|
`phabricator/resources/sshd/phabricator-ssh-hook.sh` to somewhere like
|
||||||
`/usr/libexec/phabricator-ssh-hook.sh` and edit it to have the correct
|
`/usr/libexec/phabricator-ssh-hook.sh` and edit it to have the correct
|
||||||
settings. Then make it owned by `root` and restrict editing:
|
settings.
|
||||||
|
|
||||||
sudo chown root /path/to/phabricator-ssh-hook.sh
|
Both the script itself **and** the parent directory the script resides in must
|
||||||
sudo chmod 755 /path/to/phabricator-ssh-hook.sh
|
be owned by `root`, and the script must have `755` permissions:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo chown root /path/to/somewhere/
|
||||||
|
$ sudo chown root /path/to/somewhere/phabricator-ssh-hook.sh
|
||||||
|
$ sudo chmod 755 /path/to/somewhere/phabricator-ssh-hook.sh
|
||||||
|
```
|
||||||
|
|
||||||
If you don't do this, `sshd` will refuse to execute the hook.
|
If you don't do this, `sshd` will refuse to execute the hook.
|
||||||
|
|
||||||
|
@ -215,34 +287,38 @@ Open the file and edit the `AuthorizedKeysCommand`,
|
||||||
`AuthorizedKeysCommandUser`, and `AllowUsers` settings to be correct for your
|
`AuthorizedKeysCommandUser`, and `AllowUsers` settings to be correct for your
|
||||||
system.
|
system.
|
||||||
|
|
||||||
|
This configuration file also specifies the `Port` the service should run on.
|
||||||
|
If you intend to run on a non-default port, adjust it now.
|
||||||
|
|
||||||
**Start SSHD**: Now, start the Phabricator `sshd`:
|
**Start SSHD**: Now, start the Phabricator `sshd`:
|
||||||
|
|
||||||
sudo /path/to/sshd -f /path/to/sshd_config.phabricator
|
sudo /path/to/sshd -f /path/to/sshd_config.phabricator
|
||||||
|
|
||||||
If you did everything correctly, you should be able to run this:
|
If you did everything correctly, you should be able to run this command:
|
||||||
|
|
||||||
echo {} | ssh vcs-user@phabricator.yourcompany.com conduit conduit.ping
|
```
|
||||||
|
$ echo {} | ssh vcs-user@phabricator.yourcompany.com conduit conduit.ping
|
||||||
|
```
|
||||||
|
|
||||||
...and get a response like this:
|
...and get a response like this:
|
||||||
|
|
||||||
{"result":"orbital","error_code":null,"error_info":null}
|
```lang=json
|
||||||
|
{"result":"phabricator.yourcompany.com","error_code":null,"error_info":null}
|
||||||
|
```
|
||||||
|
|
||||||
(If you get an authentication error, make sure you added your public key in
|
If you get an authentication error, make sure you added your public key in
|
||||||
**Settings > SSH Public Keys**.) If you're having trouble, check the
|
{nav Settings > SSH Public Keys}. If you're having trouble, check the
|
||||||
troubleshooting section below.
|
troubleshooting section below.
|
||||||
|
|
||||||
= Authentication Over HTTP =
|
Authentication Over SSH
|
||||||
|
=======================
|
||||||
|
|
||||||
To authenticate over HTTP, users should configure a **VCS Password** in the
|
To authenticate over SSH, users should add their public keys under
|
||||||
**Settings** screen. This panel is available only if `diffusion.allow-http-auth`
|
{nav Settings > SSH Public Keys}.
|
||||||
is enabled.
|
|
||||||
|
|
||||||
= Authentication Over SSH =
|
|
||||||
|
|
||||||
To authenticate over SSH, users should add **SSH Public Keys** in the
|
Cloning a Repository
|
||||||
**Settings** screen.
|
====================
|
||||||
|
|
||||||
= Cloning a Repository =
|
|
||||||
|
|
||||||
If you've already set up a hosted repository, you can try cloning it now. To
|
If you've already set up a hosted repository, you can try cloning it now. To
|
||||||
do this, browse to the repository's main screen in Diffusion. You should see
|
do this, browse to the repository's main screen in Diffusion. You should see
|
||||||
|
@ -253,13 +329,15 @@ To clone the repository, just run the appropriate command.
|
||||||
If you don't see the commands or running them doesn't work, see below for tips
|
If you don't see the commands or running them doesn't work, see below for tips
|
||||||
on troubleshooting.
|
on troubleshooting.
|
||||||
|
|
||||||
= Troubleshooting HTTP =
|
|
||||||
|
Troubleshooting HTTP
|
||||||
|
====================
|
||||||
|
|
||||||
Some general tips for troubleshooting problems with HTTP:
|
Some general tips for troubleshooting problems with HTTP:
|
||||||
|
|
||||||
- Make sure `diffusion.allow-http-auth` is enabled in your Phabricator config.
|
- Make sure `diffusion.allow-http-auth` is enabled in your Phabricator config.
|
||||||
- Make sure HTTP serving is enabled for the repository you're trying to clone.
|
- Make sure HTTP serving is enabled for the repository you're trying to
|
||||||
You can find this in {nav Edit Repository > Hosting}.
|
clone. You can find this in {nav Edit Repository > Hosting}.
|
||||||
- Make sure you've configured a VCS password. This is separate from your main
|
- Make sure you've configured a VCS password. This is separate from your main
|
||||||
account password. You can configure this in {nav Settings > VCS Password}.
|
account password. You can configure this in {nav Settings > VCS Password}.
|
||||||
- Make sure the main repository screen in Diffusion shows a clone/checkout
|
- Make sure the main repository screen in Diffusion shows a clone/checkout
|
||||||
|
@ -281,7 +359,8 @@ with the HTTP response is likely to be useful:
|
||||||
|
|
||||||
In many cases, this can give you more information about what's wrong.
|
In many cases, this can give you more information about what's wrong.
|
||||||
|
|
||||||
= Troubleshooting SSH =
|
Troubleshooting SSH
|
||||||
|
===================
|
||||||
|
|
||||||
Some general tips for troubleshooting problems with SSH:
|
Some general tips for troubleshooting problems with SSH:
|
||||||
|
|
||||||
|
@ -305,11 +384,11 @@ Some general tips for troubleshooting problems with SSH:
|
||||||
- Check your `phabricator-ssh-hook.sh` file for proper settings.
|
- Check your `phabricator-ssh-hook.sh` file for proper settings.
|
||||||
- Check your `sshd_config.phabricator` file for proper settings.
|
- Check your `sshd_config.phabricator` file for proper settings.
|
||||||
|
|
||||||
To troubleshoot SSH setup: connect to the server with `ssh`, without running
|
To troubleshoot SSH setup: connect to the server with `ssh`, without running a
|
||||||
a command. You may need to use the `-T` flag. You should see a message like
|
command. You may need to use the `-T` flag, and will need to use `-p` if you
|
||||||
this one:
|
are running on a nonstandard port. You should see a message like this one:
|
||||||
|
|
||||||
$ ssh -T dweller@secure.phabricator.com
|
$ ssh -T -p 2222 vcs-user@phabricator.yourcompany.com
|
||||||
phabricator-ssh-exec: Welcome to Phabricator.
|
phabricator-ssh-exec: Welcome to Phabricator.
|
||||||
|
|
||||||
You are logged in as alincoln.
|
You are logged in as alincoln.
|
||||||
|
@ -332,8 +411,8 @@ settings:
|
||||||
- You're connecting as the `vcs-user`.
|
- You're connecting as the `vcs-user`.
|
||||||
- The `vcs-user` has `NP` in `/etc/shadow`.
|
- The `vcs-user` has `NP` in `/etc/shadow`.
|
||||||
- The `vcs-user` has `/bin/sh` or some other valid shell in `/etc/passwd`.
|
- The `vcs-user` has `/bin/sh` or some other valid shell in `/etc/passwd`.
|
||||||
- Your SSH key is correct, and you've added it to Phabricator in the Settings
|
- Your SSH private key is correct, and you've added the corresponding
|
||||||
panel.
|
public key to Phabricator in the Settings panel.
|
||||||
|
|
||||||
If you can get this far, but can't execute VCS commands like `git clone`, there
|
If you can get this far, but can't execute VCS commands like `git clone`, there
|
||||||
is probably an issue with your `sudoers` configuration. Check:
|
is probably an issue with your `sudoers` configuration. Check:
|
||||||
|
@ -351,7 +430,7 @@ It may also be helpful to run `sshd` in debug mode:
|
||||||
$ /path/to/sshd -d -d -d -f /path/to/sshd_config.phabricator
|
$ /path/to/sshd -d -d -d -f /path/to/sshd_config.phabricator
|
||||||
|
|
||||||
This will run it in the foreground and emit a large amount of debugging
|
This will run it in the foreground and emit a large amount of debugging
|
||||||
information.
|
information when you connect to it.
|
||||||
|
|
||||||
Finally, you can usually test that `sudoers` is configured correctly by
|
Finally, you can usually test that `sudoers` is configured correctly by
|
||||||
doing something like this:
|
doing something like this:
|
||||||
|
@ -363,7 +442,9 @@ That will try to run the binary via `sudo` in a manner similar to the way that
|
||||||
Phabricator will run it. This can give you better error messages about issues
|
Phabricator will run it. This can give you better error messages about issues
|
||||||
with `sudoers` configuration.
|
with `sudoers` configuration.
|
||||||
|
|
||||||
= Miscellaneous Troubleshooting =
|
|
||||||
|
Miscellaneous Troubleshooting
|
||||||
|
=============================
|
||||||
|
|
||||||
- If you're getting an error about `svnlook` not being found, add the path
|
- If you're getting an error about `svnlook` not being found, add the path
|
||||||
where `svnlook` is located to the Phabricator configuration
|
where `svnlook` is located to the Phabricator configuration
|
||||||
|
@ -371,6 +452,54 @@ with `sudoers` configuration.
|
||||||
is caused by SVN wiping the environment (including PATH) when invoking
|
is caused by SVN wiping the environment (including PATH) when invoking
|
||||||
commit hooks.
|
commit hooks.
|
||||||
|
|
||||||
|
|
||||||
|
Moving the sshd Port
|
||||||
|
====================
|
||||||
|
|
||||||
|
If you want to move the standard (administrative) `sshd` to a different port to
|
||||||
|
make Phabricator repository URIs cleaner, this section has some tips.
|
||||||
|
|
||||||
|
This is optional, and it is normally easier to do this by putting a load
|
||||||
|
balancer in front of Phabricator and having it accept TCP traffic on port 22
|
||||||
|
and forward it to some other port.
|
||||||
|
|
||||||
|
When moving `sshd`, be careful when editing the configuration. If you get it
|
||||||
|
wrong, you may lock yourself out of the machine. Restarting `sshd` generally
|
||||||
|
will not interrupt existing connections, but you should exercise caution. Two
|
||||||
|
strategies you can use to mitigate this risk are: smoke-test configuration by
|
||||||
|
starting a second `sshd`; and use a `screen` session which automatically
|
||||||
|
repairs configuration unless stopped.
|
||||||
|
|
||||||
|
To smoke-test a configuration, just start another `sshd` using the `-f` flag:
|
||||||
|
|
||||||
|
sudo /path/to/sshd -f /path/to/config_file.edited
|
||||||
|
|
||||||
|
You can then connect and make sure the edited config file is valid before
|
||||||
|
replacing your primary configuration file.
|
||||||
|
|
||||||
|
To automatically repair configuration, start a `screen` session with a command
|
||||||
|
like this in it:
|
||||||
|
|
||||||
|
sleep 60 ; mv sshd_config.good sshd_config ; /etc/init.d/sshd restart
|
||||||
|
|
||||||
|
The specific command may vary for your system, but the general idea is to have
|
||||||
|
the machine automatically restore configuration after some period of time if
|
||||||
|
you don't stop it. If you lock yourself out, this can fix things automatically.
|
||||||
|
|
||||||
|
Now that you're ready to edit your configuration, open up your `sshd` config
|
||||||
|
(often `/etc/ssh/sshd_config`) and change the `Port` setting to some other port,
|
||||||
|
like `222` (you can choose any port other than 22).
|
||||||
|
|
||||||
|
Port 222
|
||||||
|
|
||||||
|
Very carefully, restart `sshd`. Verify that you can connect on the new port:
|
||||||
|
|
||||||
|
ssh -p 222 ...
|
||||||
|
|
||||||
|
Now you can move the Phabricator `sshd` to port 22, then adjust the value
|
||||||
|
for `diffusion.ssh-port` in your Phabricator configuration.
|
||||||
|
|
||||||
|
|
||||||
No Direct Pushes
|
No Direct Pushes
|
||||||
================
|
================
|
||||||
|
|
||||||
|
@ -406,7 +535,8 @@ document provides instructions for configuring. Its absence indicates that the
|
||||||
request did not pass through Phabricator.
|
request did not pass through Phabricator.
|
||||||
|
|
||||||
|
|
||||||
= Next Steps =
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
Once hosted repositories are set up:
|
Once hosted repositories are set up:
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ Overview
|
||||||
WARNING: This document describes a feature which is still under development,
|
WARNING: This document describes a feature which is still under development,
|
||||||
and is not necessarily accurate or complete.
|
and is not necessarily accurate or complete.
|
||||||
|
|
||||||
Phabricator can host, observe, mirror, and proxy repositories. For example,
|
Phabricator can create, host, observe, mirror, proxy, and import repositories.
|
||||||
here are some supported use cases:
|
For example, you can:
|
||||||
|
|
||||||
**Host Repositories**: Phabricator can host repositories locally. Phabricator
|
**Host Repositories**: Phabricator can host repositories locally. Phabricator
|
||||||
maintains the writable master version of the repository, and you can push and
|
maintains the writable master version of the repository, and you can push and
|
||||||
|
@ -45,3 +45,231 @@ configuring URIs and marking them to be fetched from, mirrored to, clonable,
|
||||||
and so on. By configuring all the URIs that a repository should interact with
|
and so on. By configuring all the URIs that a repository should interact with
|
||||||
and expose to users, you configure the read, write, and mirroring behavior
|
and expose to users, you configure the read, write, and mirroring behavior
|
||||||
of the repository.
|
of the repository.
|
||||||
|
|
||||||
|
The remainder of this document walks through this configuration in greater
|
||||||
|
detail.
|
||||||
|
|
||||||
|
|
||||||
|
Host a Repository
|
||||||
|
=================
|
||||||
|
|
||||||
|
You can create new repositories that Phabricator will host, like you would
|
||||||
|
create repositories on services like GitHub or Bitbucket. Phabricator will
|
||||||
|
serve a read-write copy of the repository and you can clone it from Phabricator
|
||||||
|
and push changes to Phabricator.
|
||||||
|
|
||||||
|
If you haven't already, you may need to configure Phabricator for hosting
|
||||||
|
before you can create your first hosted repository. For a detailed guide,
|
||||||
|
see @{article:Diffusion User Guide: Repository Hosting}.
|
||||||
|
|
||||||
|
This is the default mode for new repositories. To host a repository:
|
||||||
|
|
||||||
|
- Create a new repository.
|
||||||
|
- Activate it.
|
||||||
|
|
||||||
|
Phabricator will create an empty repository and allow you to fetch from it and
|
||||||
|
push to it.
|
||||||
|
|
||||||
|
|
||||||
|
Observe a Repository
|
||||||
|
====================
|
||||||
|
|
||||||
|
If you have an existing repository hosted on another service (like GitHub,
|
||||||
|
Bitbucket, or a private server) that you want to work with in Phabricator,
|
||||||
|
you can configure Phabricator to observe it.
|
||||||
|
|
||||||
|
When observing a repository, Phabricator will keep track of changes in the
|
||||||
|
remote repository and allow you to browse and interact with the repository from
|
||||||
|
the web UI in Diffusion and other applications, but you can continue hosting it
|
||||||
|
elsewhere.
|
||||||
|
|
||||||
|
To observe a repository:
|
||||||
|
|
||||||
|
- Create a new repository, but don't activate it yet.
|
||||||
|
- Add the remote URI you want to observe as a repository URI.
|
||||||
|
- Set the **I/O Type** for the URI to **Observe**.
|
||||||
|
- If necessary, configure a credential.
|
||||||
|
- Activate the repository.
|
||||||
|
|
||||||
|
Phabricator will perform an initial import of the repository, creating a local
|
||||||
|
read-only copy. Once this process completes, it will continue keeping track of
|
||||||
|
changes in the remote, fetching them, and reflecting them in the UI.
|
||||||
|
|
||||||
|
|
||||||
|
Mirror a Repository
|
||||||
|
===================
|
||||||
|
|
||||||
|
NOTE: Mirroring is not supported in Subversion.
|
||||||
|
|
||||||
|
You can create a read-only mirror of an existing repository. Phabricator will
|
||||||
|
push all changes made to the repository to the mirror.
|
||||||
|
|
||||||
|
For example, if you have a repository hosted in Phabricator that you want to
|
||||||
|
mirror to GitHub, you can configure Phabricator to automatically maintain the
|
||||||
|
mirror. This is how the upstream repositories are set up.
|
||||||
|
|
||||||
|
You can mirror any repository, even if Phabricator is only observing it and not
|
||||||
|
hosting it directly.
|
||||||
|
|
||||||
|
To begin mirroring a repository:
|
||||||
|
|
||||||
|
- Create a hosted or observed repository by following the relevant
|
||||||
|
instructions above.
|
||||||
|
- Add the remote URI you want to mirror to as a repository URI.
|
||||||
|
- Set the **I/O Type** for the URI to **Mirror**.
|
||||||
|
- If necessary, configure a credential.
|
||||||
|
|
||||||
|
To stop mirroring:
|
||||||
|
|
||||||
|
- Disable the mirror URI; or
|
||||||
|
- Change the **I/O Type** for the URI to **None**.
|
||||||
|
|
||||||
|
|
||||||
|
Import a Repository
|
||||||
|
===================
|
||||||
|
|
||||||
|
If you have an existing repository that you want to move so it is hosted on
|
||||||
|
Phabricator, there are three ways to do it:
|
||||||
|
|
||||||
|
**Push Everything**: //(Git, Mercurial)// Create a new empty hosted repository
|
||||||
|
according to the instructions above. Once the empty repository initializes,
|
||||||
|
push your entire existing repository to it.
|
||||||
|
|
||||||
|
**Observe First**: //(Git, Mercurial)// Observe the existing repository first,
|
||||||
|
according to the instructions above. Once Phabricator's copy of the repository
|
||||||
|
is fully synchronized, change the **I/O Type** for the **Observe** URI to
|
||||||
|
**None** to stop fetching changes from the remote.
|
||||||
|
|
||||||
|
By default, this will automatically make Phabricator's copy of the repository
|
||||||
|
writable, and you can begin pushing to it. If you've adjusted URI
|
||||||
|
configuration away from the defaults, you may need to set at least one URI
|
||||||
|
to **Read/Write** mode so you can push to it.
|
||||||
|
|
||||||
|
**Copy on Disk**: //(Git, Mercurial, Subversion)// Create a new empty hosted
|
||||||
|
repository according to the instructions above, but do not activate it yet.
|
||||||
|
|
||||||
|
Using the **Storage** tab, find the location of the repository's working copy
|
||||||
|
on disk, and place a working copy of the repository you wish to import there.
|
||||||
|
|
||||||
|
For Git and Mercurial, use a bare working copy for best results.
|
||||||
|
|
||||||
|
This is the only way to import a Subversion repository because only the master
|
||||||
|
copy of the repository has history.
|
||||||
|
|
||||||
|
Once you've put a working copy in the right place on disk, activate the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
|
||||||
|
Customizing Displayed Clone URIs
|
||||||
|
================================
|
||||||
|
|
||||||
|
If you have an unusual configuration and want the UI to offers users specific
|
||||||
|
clone URIs other than the URIs that Phabricator serves or interacts with, you
|
||||||
|
can add those URIs with the **I/O Type** set to **None** and then set their
|
||||||
|
**Display Type** to **Always**.
|
||||||
|
|
||||||
|
Likewise, you can set the **Display Type** of any URIs you do //not// want
|
||||||
|
to be visible to **Never**.
|
||||||
|
|
||||||
|
This allows you to precisely configure which clone URIs are shown to users for
|
||||||
|
a repository.
|
||||||
|
|
||||||
|
|
||||||
|
Reference: I/O Types
|
||||||
|
====================
|
||||||
|
|
||||||
|
This section details the available **I/O Type** options for URIs.
|
||||||
|
|
||||||
|
Each repository has some **builtin** URIs. These are URIs hosted by Phabricator
|
||||||
|
itself. The modes available for each URI depend primarily on whether it is a
|
||||||
|
builtin URI or not.
|
||||||
|
|
||||||
|
**Default**: This setting has Phabricator guess the correct option for the
|
||||||
|
URI.
|
||||||
|
|
||||||
|
For **builtin** URIs, the default behavior is //Read/Write// if the repository
|
||||||
|
is hosted, and //Read-Only// if the repository is observed.
|
||||||
|
|
||||||
|
For custom URIs, the default type is //None// because we can not automatically
|
||||||
|
guess if you want to ignore, observe, or mirror a URI and //None// is the
|
||||||
|
safest default.
|
||||||
|
|
||||||
|
**Observe**: Phabricator will observe this repository and regularly fetch any
|
||||||
|
changes made to it to a local read-only copy.
|
||||||
|
|
||||||
|
You can not observe builtin URIs because reading a repository from itself
|
||||||
|
does not make sense.
|
||||||
|
|
||||||
|
You can not add a URI in Observe mode if an existing builtin URI is in
|
||||||
|
//Read/Write// mode, because this would mean the repository had two different
|
||||||
|
authorities: the observed remote copy and the hosted local copy. Take the
|
||||||
|
other URI out of //Read/Write// mode first.
|
||||||
|
|
||||||
|
**Mirror**: Phabricator will push any changes made to this repository to the
|
||||||
|
remote URI, keeping a read-only mirror hosted at that URI up to date.
|
||||||
|
|
||||||
|
This works for both observed and hosted repositories.
|
||||||
|
|
||||||
|
This option is not available for builtin URIs because it does not make sense
|
||||||
|
to mirror a repository to itself.
|
||||||
|
|
||||||
|
It is possible to mirror a repository to another repository that is also
|
||||||
|
hosted by Phabricator by adding that other repository's URI, although this is
|
||||||
|
silly and probably very rarely of any use.
|
||||||
|
|
||||||
|
**None**: Phabricator will not fetch changes from or push changes to this URI.
|
||||||
|
For builtin URIs, it will not let users fetch changes from or push changes to
|
||||||
|
this URI.
|
||||||
|
|
||||||
|
You can use this mode to turn off an Observe URI after an import, stop a Mirror
|
||||||
|
URI from updating, or to add URIs that you're only using to customize which
|
||||||
|
clone URIs are displayed to the user but don't want Phabricator to interact
|
||||||
|
with directly.
|
||||||
|
|
||||||
|
**Read Only**: Phabricator will serve the repository from this URI in read-only
|
||||||
|
mode. Users will be able to fetch from it but will not be able to push to it.
|
||||||
|
|
||||||
|
Because Phabricator must be able to serve the repository from URIs configured
|
||||||
|
in this mode, this option is only available for builtin URIs.
|
||||||
|
|
||||||
|
**Read/Write**: Phabricator will serve the repository from this URI in
|
||||||
|
read/write mode. Users will be able to fetch from it and push to it.
|
||||||
|
|
||||||
|
URIs can not be set into this mode if another URI is set to //Observe// mode,
|
||||||
|
because that would mean the repository had two different authorities: the
|
||||||
|
observed remote copy and the hosted local copy. Take the other URI out of
|
||||||
|
//Observe// mode first.
|
||||||
|
|
||||||
|
Because Phabricator must be able to serve the repository from URIs configured
|
||||||
|
in this mode, this option is only available for builtin URIs.
|
||||||
|
|
||||||
|
|
||||||
|
Reference: Display Types
|
||||||
|
========================
|
||||||
|
|
||||||
|
This section details the available **Display Type** options for URIs.
|
||||||
|
|
||||||
|
**Default**: Phabricator will guess the correct option for the URI. It
|
||||||
|
guesses based on the configured **I/O Type** and whether the URI is
|
||||||
|
**builtin** or not.
|
||||||
|
|
||||||
|
For //Observe//, //Mirror// and //None// URIs, the default is //Never//.
|
||||||
|
|
||||||
|
For builtin URIs in //Read Only// or //Read/Write// mode, the most
|
||||||
|
human-readable URI defaults to //Always// and the others default to //Never//.
|
||||||
|
|
||||||
|
**Always**: This URI will be shown to users as a clone/checkout URI. You can
|
||||||
|
add URIs in this mode to cutomize exactly what users are shown.
|
||||||
|
|
||||||
|
**Never**: This URI will not be shown to users. You can hide less-preferred
|
||||||
|
URIs to guide users to the URIs they should be using to interact with the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- configuring Phabricator to host repositories with
|
||||||
|
@{article:Diffusion User Guide: Repository Hosting}.
|
||||||
|
|
|
@ -75,6 +75,12 @@ abstract class PhabricatorLiskDAO extends LiskDAO {
|
||||||
$connection->setReadOnly(true);
|
$connection->setReadOnly(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unless this is a script running from the CLI, prevent any query from
|
||||||
|
// running for more than 30 seconds. See T10849 for discussion.
|
||||||
|
if (php_sapi_name() != 'cli') {
|
||||||
|
$connection->setQueryTimeout(30);
|
||||||
|
}
|
||||||
|
|
||||||
return $connection;
|
return $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
final class PhabricatorGlobalLock extends PhutilLock {
|
final class PhabricatorGlobalLock extends PhutilLock {
|
||||||
|
|
||||||
private $conn;
|
private $conn;
|
||||||
|
private $isExternalConnection = false;
|
||||||
|
|
||||||
private static $pool = array();
|
private static $pool = array();
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ final class PhabricatorGlobalLock extends PhutilLock {
|
||||||
*/
|
*/
|
||||||
public function useSpecificConnection(AphrontDatabaseConnection $conn) {
|
public function useSpecificConnection(AphrontDatabaseConnection $conn) {
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
|
$this->isExternalConnection = true;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,29 +111,54 @@ final class PhabricatorGlobalLock extends PhutilLock {
|
||||||
$max_allowed_timeout = 2147483;
|
$max_allowed_timeout = 2147483;
|
||||||
queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout);
|
queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout);
|
||||||
|
|
||||||
|
$lock_name = $this->getName();
|
||||||
|
|
||||||
$result = queryfx_one(
|
$result = queryfx_one(
|
||||||
$conn,
|
$conn,
|
||||||
'SELECT GET_LOCK(%s, %f)',
|
'SELECT GET_LOCK(%s, %f)',
|
||||||
$this->getName(),
|
$lock_name,
|
||||||
$wait);
|
$wait);
|
||||||
|
|
||||||
$ok = head($result);
|
$ok = head($result);
|
||||||
if (!$ok) {
|
if (!$ok) {
|
||||||
throw new PhutilLockException($this->getName());
|
throw new PhutilLockException($lock_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$conn->rememberLock($lock_name);
|
||||||
|
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function doUnlock() {
|
protected function doUnlock() {
|
||||||
queryfx(
|
$lock_name = $this->getName();
|
||||||
$this->conn,
|
|
||||||
'SELECT RELEASE_LOCK(%s)',
|
$conn = $this->conn;
|
||||||
$this->getName());
|
|
||||||
|
try {
|
||||||
|
$result = queryfx_one(
|
||||||
|
$conn,
|
||||||
|
'SELECT RELEASE_LOCK(%s)',
|
||||||
|
$lock_name);
|
||||||
|
$conn->forgetLock($lock_name);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$result = array(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok = head($result);
|
||||||
|
if (!$ok) {
|
||||||
|
// TODO: We could throw here, but then this lock doesn't get marked
|
||||||
|
// unlocked and we throw again later when exiting. It also doesn't
|
||||||
|
// particularly matter for any current applications. For now, just
|
||||||
|
// swallow the error.
|
||||||
|
}
|
||||||
|
|
||||||
$this->conn->close();
|
|
||||||
self::$pool[] = $this->conn;
|
|
||||||
$this->conn = null;
|
$this->conn = null;
|
||||||
|
$this->isExternalConnection = false;
|
||||||
|
|
||||||
|
if (!$this->isExternalConnection) {
|
||||||
|
$conn->close();
|
||||||
|
self::$pool[] = $conn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,10 @@ body .phui-header-shell.phui-bleed-header
|
||||||
.phui-header-header .phui-header-icon {
|
.phui-header-header .phui-header-icon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
color: {$lightbluetext};
|
color: {$lightbluetext};
|
||||||
|
|
||||||
|
/* This allows the header text to be triple-clicked to select it in Firefox,
|
||||||
|
see T10905 for discussion. */
|
||||||
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phui-object-box .phui-header-tall .phui-header-header,
|
.phui-object-box .phui-header-tall .phui-header-header,
|
||||||
|
|
25
webroot/rsrc/externals/javelin/lib/Leader.js
vendored
25
webroot/rsrc/externals/javelin/lib/Leader.js
vendored
|
@ -118,6 +118,10 @@ JX.install('Leader', {
|
||||||
// Read the current leadership lease.
|
// Read the current leadership lease.
|
||||||
var lease = self._read();
|
var lease = self._read();
|
||||||
|
|
||||||
|
// Stagger these delays so that they are unlikely to race one another.
|
||||||
|
var expire_delay = 50;
|
||||||
|
var usurp_delay = 75;
|
||||||
|
|
||||||
// If the lease is good, we're all set.
|
// If the lease is good, we're all set.
|
||||||
var now = +new Date();
|
var now = +new Date();
|
||||||
if (lease.until > now) {
|
if (lease.until > now) {
|
||||||
|
@ -135,15 +139,17 @@ JX.install('Leader', {
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Set a callback to try to become the leader shortly after the
|
// Set a callback to try to become the leader shortly after the
|
||||||
// current lease expires. This lets us recover from cases where the
|
// current lease expires. This lets us quickly recover from cases
|
||||||
// leader goes missing quickly.
|
// where the leader goes missing.
|
||||||
if (self._timeoout) {
|
|
||||||
window.clearTimeout(self._timeout);
|
// In particular, this can happen in Safari if you close windows or
|
||||||
self._timeout = null;
|
// quit the browser instead of browsing away: the "pagehide" event
|
||||||
|
// does not fire when the leader is simply destroyed, so it does not
|
||||||
|
// evict itself from the throne of power.
|
||||||
|
if (!self._timeout) {
|
||||||
|
var usurp_at = (lease.until - now) + usurp_delay;
|
||||||
|
self._timeout = window.setTimeout(self._usurp, usurp_at);
|
||||||
}
|
}
|
||||||
self._timeout = window.setTimeout(
|
|
||||||
self._usurp,
|
|
||||||
(lease.until - now) + 50);
|
|
||||||
|
|
||||||
follower_callback();
|
follower_callback();
|
||||||
}
|
}
|
||||||
|
@ -174,7 +180,7 @@ JX.install('Leader', {
|
||||||
|
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
JX.bind(null, self._callIf, leader_callback, follower_callback),
|
JX.bind(null, self._callIf, leader_callback, follower_callback),
|
||||||
50);
|
expire_delay);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -306,6 +312,7 @@ JX.install('Leader', {
|
||||||
_usurp: function() {
|
_usurp: function() {
|
||||||
var self = JX.Leader;
|
var self = JX.Leader;
|
||||||
self.call(JX.bag);
|
self.call(JX.bag);
|
||||||
|
self._timeout = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue