mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 00:02:41 +01:00
(stable) Promote 2016 Week 17
This commit is contained in:
commit
ee92a3f25a
109 changed files with 4379 additions and 531 deletions
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'ce06b6f6',
|
||||
'core.pkg.css' => '04a95108',
|
||||
'core.pkg.js' => '37344f3c',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '7ba78475',
|
||||
|
@ -88,7 +88,7 @@ return array(
|
|||
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02',
|
||||
'rsrc/css/application/phortune/phortune.css' => '9149f103',
|
||||
'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad',
|
||||
'rsrc/css/application/phriction/phriction-document-css.css' => 'd1861e06',
|
||||
'rsrc/css/application/phriction/phriction-document-css.css' => '4282e4ad',
|
||||
'rsrc/css/application/policy/policy-edit.css' => '815c66f7',
|
||||
'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
|
||||
'rsrc/css/application/policy/policy.css' => '957ea14c',
|
||||
|
@ -104,7 +104,7 @@ return array(
|
|||
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
|
||||
'rsrc/css/application/uiexample/example.css' => '528b19de',
|
||||
'rsrc/css/core/core.css' => 'd0801452',
|
||||
'rsrc/css/core/remarkup.css' => '2c9ed46f',
|
||||
'rsrc/css/core/remarkup.css' => '6aae5360',
|
||||
'rsrc/css/core/syntax.css' => '9fd11da8',
|
||||
'rsrc/css/core/z-index.css' => '5b6fcf3f',
|
||||
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
|
||||
|
@ -131,7 +131,7 @@ return array(
|
|||
'rsrc/css/phui/phui-document-pro.css' => '73e45fd2',
|
||||
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
|
||||
'rsrc/css/phui/phui-document.css' => '9c71d2bf',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '04aec08f',
|
||||
'rsrc/css/phui/phui-feed-story.css' => 'd8440402',
|
||||
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
|
||||
'rsrc/css/phui/phui-form-view.css' => '6a51768e',
|
||||
'rsrc/css/phui/phui-form.css' => 'aac1d51d',
|
||||
|
@ -479,7 +479,7 @@ return array(
|
|||
'rsrc/js/core/behavior-device.js' => 'b5b36110',
|
||||
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4f6a4b4e',
|
||||
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => '8ae55229',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => '568931f3',
|
||||
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
|
||||
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
|
||||
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
|
||||
|
@ -622,7 +622,7 @@ return array(
|
|||
'javelin-behavior-editengine-reorder-fields' => 'b59e1e96',
|
||||
'javelin-behavior-error-log' => '6882e80a',
|
||||
'javelin-behavior-event-all-day' => '38dcf3c8',
|
||||
'javelin-behavior-fancy-datepicker' => '8ae55229',
|
||||
'javelin-behavior-fancy-datepicker' => '568931f3',
|
||||
'javelin-behavior-global-drag-and-drop' => 'c8e57404',
|
||||
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
|
||||
'javelin-behavior-high-security-warning' => 'a464fe03',
|
||||
|
@ -777,7 +777,7 @@ return array(
|
|||
'phabricator-object-selector-css' => '85ee8ce6',
|
||||
'phabricator-phtize' => 'd254d646',
|
||||
'phabricator-prefab' => 'e67df814',
|
||||
'phabricator-remarkup-css' => '2c9ed46f',
|
||||
'phabricator-remarkup-css' => '6aae5360',
|
||||
'phabricator-search-results-css' => '7dea472c',
|
||||
'phabricator-shaped-request' => '7cbe244b',
|
||||
'phabricator-side-menu-view-css' => '3a3d9f41',
|
||||
|
@ -807,7 +807,7 @@ return array(
|
|||
'phortune-credit-card-form-css' => '8391eb02',
|
||||
'phortune-css' => '9149f103',
|
||||
'phrequent-css' => 'ffc185ad',
|
||||
'phriction-document-css' => 'd1861e06',
|
||||
'phriction-document-css' => '4282e4ad',
|
||||
'phui-action-panel-css' => '91c7b835',
|
||||
'phui-badge-view-css' => '3baef8db',
|
||||
'phui-big-info-view-css' => 'bd903741',
|
||||
|
@ -823,7 +823,7 @@ return array(
|
|||
'phui-document-summary-view-css' => '9ca48bdf',
|
||||
'phui-document-view-css' => '9c71d2bf',
|
||||
'phui-document-view-pro-css' => '73e45fd2',
|
||||
'phui-feed-story-css' => '04aec08f',
|
||||
'phui-feed-story-css' => 'd8440402',
|
||||
'phui-font-icon-base-css' => '6449bce8',
|
||||
'phui-fontkit-css' => '9cda225e',
|
||||
'phui-form-css' => 'aac1d51d',
|
||||
|
@ -1301,6 +1301,13 @@ return array(
|
|||
'phabricator-drag-and-drop-file-upload',
|
||||
'javelin-workboard-board',
|
||||
),
|
||||
'568931f3' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'56a1ca03' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-behavior-device',
|
||||
|
@ -1554,13 +1561,6 @@ return array(
|
|||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'8ae55229' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'8bdb2835' => array(
|
||||
'phui-fontkit-css',
|
||||
),
|
||||
|
|
14
resources/sql/autopatches/20160418.repouri.1.sql
Normal file
14
resources/sql/autopatches/20160418.repouri.1.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE {$NAMESPACE}_repository.repository_uri (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
repositoryPHID VARBINARY(64) NOT NULL,
|
||||
uri VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
builtinProtocol VARCHAR(32) COLLATE {$COLLATE_TEXT},
|
||||
builtinIdentifier VARCHAR(32) COLLATE {$COLLATE_TEXT},
|
||||
ioType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
displayType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
isDisabled BOOL NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_builtin` (repositoryPHID, builtinProtocol, builtinIdentifier)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20160418.repouri.2.sql
Normal file
2
resources/sql/autopatches/20160418.repouri.2.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_repository.repository_uri
|
||||
ADD credentialPHID VARBINARY(64);
|
2
resources/sql/autopatches/20160418.repoversion.1.sql
Normal file
2
resources/sql/autopatches/20160418.repoversion.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_repository.repository_workingcopyversion
|
||||
ADD writeProperties LONGTEXT COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20160419.pushlog.1.sql
Normal file
2
resources/sql/autopatches/20160419.pushlog.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_repository.repository_pushlog
|
||||
ADD devicePHID VARBINARY(64);
|
|
@ -34,7 +34,28 @@ $pattern[] = 'StrictHostKeyChecking=no';
|
|||
$pattern[] = '-o';
|
||||
$pattern[] = 'UserKnownHostsFile=/dev/null';
|
||||
|
||||
$as_device = getenv('PHABRICATOR_AS_DEVICE');
|
||||
$credential_phid = getenv('PHABRICATOR_CREDENTIAL');
|
||||
|
||||
if ($as_device) {
|
||||
$device = AlmanacKeys::getLiveDevice();
|
||||
if (!$device) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Attempting to create an SSH connection that authenticates with '.
|
||||
'the current device, but this host is not configured as a cluster '.
|
||||
'device.'));
|
||||
}
|
||||
|
||||
if ($credential_phid) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Attempting to proxy an SSH connection that authenticates with '.
|
||||
'both the current device and a specific credential. These options '.
|
||||
'are mutually exclusive.'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($credential_phid) {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
$key = PassphraseSSHKey::loadFromPHID($credential_phid, $viewer);
|
||||
|
@ -45,6 +66,13 @@ if ($credential_phid) {
|
|||
$arguments[] = $key->getKeyfileEnvelope();
|
||||
}
|
||||
|
||||
if ($as_device) {
|
||||
$pattern[] = '-l %R';
|
||||
$arguments[] = AlmanacKeys::getClusterSSHUser();
|
||||
$pattern[] = '-i %R';
|
||||
$arguments[] = AlmanacKeys::getKeyPath('device.key');
|
||||
}
|
||||
|
||||
$port = $args->getArg('port');
|
||||
if ($port) {
|
||||
$pattern[] = '-p %d';
|
||||
|
|
|
@ -153,15 +153,11 @@ try {
|
|||
->splitArguments($original_command);
|
||||
|
||||
if ($device) {
|
||||
// If we're authenticating as a device, the first argument may be a
|
||||
// "@username" argument to act as a particular user.
|
||||
$first_argument = head($original_argv);
|
||||
if (preg_match('/^@/', $first_argument)) {
|
||||
$act_as_name = array_shift($original_argv);
|
||||
if (!preg_match('/^@/', $act_as_name)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Commands executed by devices must identify an acting user in the '.
|
||||
'first command argument. This request was not constructed '.
|
||||
'properly.'));
|
||||
}
|
||||
|
||||
$act_as_name = substr($act_as_name, 1);
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
|
@ -174,11 +170,20 @@ try {
|
|||
'username ("%s"). There is no user with this username.',
|
||||
$act_as_name));
|
||||
}
|
||||
} else {
|
||||
$user = PhabricatorUser::getOmnipotentUser();
|
||||
}
|
||||
}
|
||||
|
||||
if ($user->isOmnipotent()) {
|
||||
$user_name = 'device/'.$device->getName();
|
||||
} else {
|
||||
$user_name = $user->getUsername();
|
||||
}
|
||||
|
||||
$ssh_log->setData(
|
||||
array(
|
||||
'u' => $user->getUsername(),
|
||||
'u' => $user_name,
|
||||
'P' => $user->getPHID(),
|
||||
));
|
||||
|
||||
|
@ -187,7 +192,7 @@ try {
|
|||
pht(
|
||||
'Your account ("%s") does not have permission to establish SSH '.
|
||||
'sessions. Visit the web interface for more information.',
|
||||
$user->getUsername()));
|
||||
$user_name));
|
||||
}
|
||||
|
||||
$workflows = id(new PhutilClassMapQuery())
|
||||
|
@ -206,7 +211,7 @@ try {
|
|||
"Usually, you should run a command like `%s` or `%s` ".
|
||||
"rather than connecting directly with SSH.\n\n".
|
||||
"Supported commands are: %s.",
|
||||
$user->getUsername(),
|
||||
$user_name,
|
||||
'git clone',
|
||||
'hg push',
|
||||
implode(', ', array_keys($workflows))));
|
||||
|
|
|
@ -570,6 +570,8 @@ phutil_register_library_map(array(
|
|||
'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php',
|
||||
'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php',
|
||||
'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php',
|
||||
'DiffusionCommandEngine' => 'applications/diffusion/protocol/DiffusionCommandEngine.php',
|
||||
'DiffusionCommandEngineTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php',
|
||||
'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php',
|
||||
'DiffusionCommitAuthorHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorHeraldField.php',
|
||||
'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php',
|
||||
|
@ -634,6 +636,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionGitBlameQuery' => 'applications/diffusion/query/blame/DiffusionGitBlameQuery.php',
|
||||
'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php',
|
||||
'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php',
|
||||
'DiffusionGitCommandEngine' => 'applications/diffusion/protocol/DiffusionGitCommandEngine.php',
|
||||
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
|
||||
'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php',
|
||||
'DiffusionGitLFSResponse' => 'applications/diffusion/response/DiffusionGitLFSResponse.php',
|
||||
|
@ -667,6 +670,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php',
|
||||
'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php',
|
||||
'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php',
|
||||
'DiffusionMercurialCommandEngine' => 'applications/diffusion/protocol/DiffusionMercurialCommandEngine.php',
|
||||
'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php',
|
||||
'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php',
|
||||
'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php',
|
||||
|
@ -739,6 +743,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php',
|
||||
'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php',
|
||||
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
|
||||
'DiffusionRepositoryBasicsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php',
|
||||
'DiffusionRepositoryByIDRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryByIDRemarkupRule.php',
|
||||
'DiffusionRepositoryClusterManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php',
|
||||
'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
|
||||
|
@ -750,27 +755,35 @@ phutil_register_library_map(array(
|
|||
'DiffusionRepositoryEditAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php',
|
||||
'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php',
|
||||
'DiffusionRepositoryEditBranchesController' => 'applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php',
|
||||
'DiffusionRepositoryEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositoryEditConduitAPIMethod.php',
|
||||
'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
|
||||
'DiffusionRepositoryEditDangerousController' => 'applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php',
|
||||
'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php',
|
||||
'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php',
|
||||
'DiffusionRepositoryEditEngine' => 'applications/diffusion/editor/DiffusionRepositoryEditEngine.php',
|
||||
'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php',
|
||||
'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php',
|
||||
'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php',
|
||||
'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php',
|
||||
'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php',
|
||||
'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php',
|
||||
'DiffusionRepositoryEditproController' => 'applications/diffusion/controller/DiffusionRepositoryEditproController.php',
|
||||
'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php',
|
||||
'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php',
|
||||
'DiffusionRepositoryManageController' => 'applications/diffusion/controller/DiffusionRepositoryManageController.php',
|
||||
'DiffusionRepositoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryManagementPanel.php',
|
||||
'DiffusionRepositoryNewController' => 'applications/diffusion/controller/DiffusionRepositoryNewController.php',
|
||||
'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
|
||||
'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php',
|
||||
'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php',
|
||||
'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
|
||||
'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php',
|
||||
'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php',
|
||||
'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
|
||||
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
||||
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
||||
'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
|
||||
'DiffusionRepositoryURIsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php',
|
||||
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
|
||||
'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
|
||||
'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
|
||||
|
@ -779,6 +792,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
|
||||
'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
|
||||
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
|
||||
'DiffusionSubversionCommandEngine' => 'applications/diffusion/protocol/DiffusionSubversionCommandEngine.php',
|
||||
'DiffusionSubversionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionSSHWorkflow.php',
|
||||
'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php',
|
||||
'DiffusionSubversionWireProtocol' => 'applications/diffusion/protocol/DiffusionSubversionWireProtocol.php',
|
||||
|
@ -2041,6 +2055,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php',
|
||||
'PhabricatorConfigClusterDatabasesController' => 'applications/config/controller/PhabricatorConfigClusterDatabasesController.php',
|
||||
'PhabricatorConfigClusterNotificationsController' => 'applications/config/controller/PhabricatorConfigClusterNotificationsController.php',
|
||||
'PhabricatorConfigClusterRepositoriesController' => 'applications/config/controller/PhabricatorConfigClusterRepositoriesController.php',
|
||||
'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php',
|
||||
'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php',
|
||||
'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
|
||||
|
@ -3169,6 +3184,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryManagementPullWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php',
|
||||
'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php',
|
||||
'PhabricatorRepositoryManagementReparseWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php',
|
||||
'PhabricatorRepositoryManagementThawWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php',
|
||||
'PhabricatorRepositoryManagementUpdateWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php',
|
||||
'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php',
|
||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php',
|
||||
|
@ -3208,6 +3224,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
|
||||
'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
|
||||
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
|
||||
'PhabricatorRepositoryURI' => 'applications/repository/storage/PhabricatorRepositoryURI.php',
|
||||
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
|
||||
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
|
||||
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
|
||||
|
@ -3905,6 +3922,7 @@ phutil_register_library_map(array(
|
|||
'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php',
|
||||
'PhrictionInfoConduitAPIMethod' => 'applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php',
|
||||
'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php',
|
||||
'PhrictionMarkupPreviewController' => 'applications/phriction/controller/PhrictionMarkupPreviewController.php',
|
||||
'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php',
|
||||
'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php',
|
||||
'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php',
|
||||
|
@ -4760,6 +4778,8 @@ phutil_register_library_map(array(
|
|||
'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionChangeController' => 'DiffusionController',
|
||||
'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup',
|
||||
'DiffusionCommandEngine' => 'Phobject',
|
||||
'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase',
|
||||
'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField',
|
||||
'DiffusionCommitAuthorHeraldField' => 'DiffusionCommitHeraldField',
|
||||
'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField',
|
||||
|
@ -4824,6 +4844,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionGitBlameQuery' => 'DiffusionBlameQuery',
|
||||
'DiffusionGitBranch' => 'Phobject',
|
||||
'DiffusionGitBranchTestCase' => 'PhabricatorTestCase',
|
||||
'DiffusionGitCommandEngine' => 'DiffusionCommandEngine',
|
||||
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
|
||||
'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow',
|
||||
'DiffusionGitLFSResponse' => 'AphrontResponse',
|
||||
|
@ -4857,6 +4878,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionLowLevelQuery' => 'Phobject',
|
||||
'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery',
|
||||
'DiffusionMercurialCommandEngine' => 'DiffusionCommandEngine',
|
||||
'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery',
|
||||
'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery',
|
||||
'DiffusionMercurialRequest' => 'DiffusionRequest',
|
||||
|
@ -4929,6 +4951,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionRefTableController' => 'DiffusionController',
|
||||
'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionRenameHistoryQuery' => 'Phobject',
|
||||
'DiffusionRepositoryBasicsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||
'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'DiffusionRepositoryClusterManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||
'DiffusionRepositoryController' => 'DiffusionController',
|
||||
|
@ -4940,27 +4963,35 @@ phutil_register_library_map(array(
|
|||
'DiffusionRepositoryEditAutomationController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditBranchesController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'DiffusionRepositoryEditController' => 'DiffusionController',
|
||||
'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine',
|
||||
'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditproController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||
'DiffusionRepositoryListController' => 'DiffusionController',
|
||||
'DiffusionRepositoryManageController' => 'DiffusionController',
|
||||
'DiffusionRepositoryManagementPanel' => 'Phobject',
|
||||
'DiffusionRepositoryNewController' => 'DiffusionController',
|
||||
'DiffusionRepositoryPath' => 'Phobject',
|
||||
'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||
'DiffusionRepositoryRef' => 'Phobject',
|
||||
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||
'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||
'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryTag' => 'Phobject',
|
||||
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
||||
'DiffusionRepositoryURIsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||
'DiffusionRequest' => 'Phobject',
|
||||
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionResolveUserQuery' => 'Phobject',
|
||||
|
@ -4969,6 +5000,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionServeController' => 'DiffusionController',
|
||||
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'DiffusionSetupException' => 'Exception',
|
||||
'DiffusionSubversionCommandEngine' => 'DiffusionCommandEngine',
|
||||
'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow',
|
||||
'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow',
|
||||
'DiffusionSubversionWireProtocol' => 'Phobject',
|
||||
|
@ -6471,6 +6503,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConfigCacheController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigClusterDatabasesController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigClusterNotificationsController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigClusterRepositoriesController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule',
|
||||
'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
|
||||
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
|
||||
|
@ -7746,6 +7779,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'PhabricatorSpacesInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
),
|
||||
'PhabricatorRepositoryAuditRequest' => array(
|
||||
'PhabricatorRepositoryDAO',
|
||||
|
@ -7803,6 +7837,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryManagementPullWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementReparseWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementThawWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementUpdateWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
|
||||
|
@ -7857,6 +7892,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorRepositoryType' => 'Phobject',
|
||||
'PhabricatorRepositoryURI' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositoryURINormalizer' => 'Phobject',
|
||||
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
|
||||
|
@ -8702,6 +8738,7 @@ phutil_register_library_map(array(
|
|||
'PhrictionHistoryController' => 'PhrictionController',
|
||||
'PhrictionInfoConduitAPIMethod' => 'PhrictionConduitAPIMethod',
|
||||
'PhrictionListController' => 'PhrictionController',
|
||||
'PhrictionMarkupPreviewController' => 'PhabricatorController',
|
||||
'PhrictionMoveController' => 'PhrictionController',
|
||||
'PhrictionNewController' => 'PhrictionController',
|
||||
'PhrictionRemarkupRule' => 'PhutilRemarkupRule',
|
||||
|
|
|
@ -132,7 +132,7 @@ final class AlmanacServiceViewController
|
|||
->setHideServiceColumn(true);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('SERVICE BINDINGS'))
|
||||
->setHeader(pht('Service Bindings'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
|
|
|
@ -48,4 +48,22 @@ final class AlmanacKeys extends Phobject {
|
|||
return $device;
|
||||
}
|
||||
|
||||
public static function getClusterSSHUser() {
|
||||
// NOTE: When instancing, we currently use the SSH username to figure out
|
||||
// which instance you are connecting to. We can't use the host name because
|
||||
// we have no way to tell which host you think you're reaching: the SSH
|
||||
// protocol does not have a mechanism like a "Host" header.
|
||||
$username = PhabricatorEnv::getEnvConfig('cluster.instance');
|
||||
if (strlen($username)) {
|
||||
return $username;
|
||||
}
|
||||
|
||||
$username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
|
||||
if (strlen($username)) {
|
||||
return $username;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@ final class PhabricatorAphlictManagementStatusWorkflow
|
|||
$this
|
||||
->setName('status')
|
||||
->setSynopsis(pht('Show the status of the notification server.'))
|
||||
->setArguments(array());
|
||||
->setArguments($this->getLaunchArguments());
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$this->parseLaunchArguments($args);
|
||||
$console = PhutilConsole::getConsole();
|
||||
$pid = $this->getPID();
|
||||
|
||||
|
|
|
@ -301,7 +301,7 @@ abstract class PhabricatorAphlictManagementWorkflow
|
|||
return $pid;
|
||||
}
|
||||
|
||||
final public function cleanup($signo = '?') {
|
||||
final public function cleanup($signo = null) {
|
||||
global $g_future;
|
||||
if ($g_future) {
|
||||
$g_future->resolveKill();
|
||||
|
@ -310,6 +310,11 @@ abstract class PhabricatorAphlictManagementWorkflow
|
|||
|
||||
Filesystem::remove($this->getPIDPath());
|
||||
|
||||
if ($signo !== null) {
|
||||
$signame = phutil_get_signal_name($signo);
|
||||
error_log("Caught signal {$signame}, exiting.");
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -428,6 +433,15 @@ abstract class PhabricatorAphlictManagementWorkflow
|
|||
$console = PhutilConsole::getConsole();
|
||||
$this->willLaunch();
|
||||
|
||||
$log = $this->getOverseerLogPath();
|
||||
if ($log !== null) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Writing logs to: %s',
|
||||
$log));
|
||||
}
|
||||
|
||||
$pid = pcntl_fork();
|
||||
if ($pid < 0) {
|
||||
throw new Exception(
|
||||
|
@ -439,6 +453,12 @@ abstract class PhabricatorAphlictManagementWorkflow
|
|||
exit(0);
|
||||
}
|
||||
|
||||
// Redirect process errors to the error log. If we do not do this, any
|
||||
// error the `aphlict` process itself encounters vanishes into thin air.
|
||||
if ($log !== null) {
|
||||
ini_set('error_log', $log);
|
||||
}
|
||||
|
||||
// When we fork, the child process will inherit its parent's set of open
|
||||
// file descriptors. If the parent process of bin/aphlict is waiting for
|
||||
// bin/aphlict's file descriptors to close, it will be stuck waiting on
|
||||
|
@ -529,4 +549,15 @@ abstract class PhabricatorAphlictManagementWorkflow
|
|||
$server_argv);
|
||||
}
|
||||
|
||||
private function getOverseerLogPath() {
|
||||
// For now, just return the first log. We could refine this eventually.
|
||||
$logs = idx($this->configData, 'logs', array());
|
||||
|
||||
foreach ($logs as $log) {
|
||||
return $log['path'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ final class PhabricatorAuthRegisterController
|
|||
$account_key = $request->getURIData('akey');
|
||||
|
||||
if ($request->getUser()->isLoggedIn()) {
|
||||
return $this->renderError(pht('You are already logged in.'));
|
||||
return id(new AphrontRedirectResponse())->setURI('/');
|
||||
}
|
||||
|
||||
$is_setup = false;
|
||||
|
|
|
@ -82,7 +82,8 @@ final class PhabricatorCalendarEventCancelController
|
|||
} else if ($is_parent) {
|
||||
$title = pht('Reinstate Recurrence');
|
||||
$paragraph = pht(
|
||||
'Reinstate the entire series of recurring events?');
|
||||
'Reinstate all instances of this recurrence
|
||||
that have not been individually cancelled?');
|
||||
$cancel = pht("Don't Reinstate Recurrence");
|
||||
$submit = pht('Reinstate Recurrence');
|
||||
} else {
|
||||
|
|
|
@ -459,7 +459,7 @@ final class PhabricatorCalendarEventEditController
|
|||
}
|
||||
|
||||
$projects = id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($projects)
|
||||
->setUser($viewer)
|
||||
|
|
|
@ -219,8 +219,8 @@ final class PhabricatorCalendarEventViewController
|
|||
$reinstate_label = pht('Reinstate This Instance');
|
||||
$cancel_disabled = (!$can_edit || $can_reinstate);
|
||||
} else if ($event->getIsRecurrenceParent()) {
|
||||
$cancel_label = pht('Cancel Recurrence');
|
||||
$reinstate_label = pht('Reinstate Recurrence');
|
||||
$cancel_label = pht('Cancel All');
|
||||
$reinstate_label = pht('Reinstate All');
|
||||
$cancel_disabled = !$can_edit;
|
||||
} else {
|
||||
$cancel_label = pht('Cancel Event');
|
||||
|
|
|
@ -271,8 +271,11 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$attendees = array();
|
||||
|
||||
foreach ($event->getInvitees() as $invitee) {
|
||||
$status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
|
||||
if ($invitee->getStatus() === $status_attending) {
|
||||
$attendees[] = $invitee->getInviteePHID();
|
||||
}
|
||||
}
|
||||
|
||||
if ($event->getIsGhostEvent()) {
|
||||
$title_text = $event->getMonogram()
|
||||
|
|
|
@ -208,6 +208,11 @@ final class CelerityResourceMap extends Phobject {
|
|||
}
|
||||
|
||||
|
||||
public function getHashForName($name) {
|
||||
return idx($this->nameMap, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the absolute URI for a resource, identified by hash.
|
||||
* This method is fairly low-level and ignores packaging.
|
||||
|
|
|
@ -31,7 +31,11 @@ final class CelerityPhabricatorResourceController
|
|||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
return $this->serveResource($this->path);
|
||||
return $this->serveResource(
|
||||
array(
|
||||
'path' => $this->path,
|
||||
'hash' => $this->hash,
|
||||
));
|
||||
}
|
||||
|
||||
protected function buildResourceTransformer() {
|
||||
|
|
|
@ -24,7 +24,10 @@ abstract class CelerityResourceController extends PhabricatorController {
|
|||
|
||||
abstract public function getCelerityResourceMap();
|
||||
|
||||
protected function serveResource($path, $package_hash = null) {
|
||||
protected function serveResource(array $spec) {
|
||||
$path = $spec['path'];
|
||||
$hash = idx($spec, 'hash');
|
||||
|
||||
// Sanity checking to keep this from exposing anything sensitive, since it
|
||||
// ultimately boils down to disk reads.
|
||||
if (preg_match('@(//|\.\.)@', $path)) {
|
||||
|
@ -40,18 +43,24 @@ abstract class CelerityResourceController extends PhabricatorController {
|
|||
|
||||
$dev_mode = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
|
||||
|
||||
if (AphrontRequest::getHTTPHeader('If-Modified-Since') && !$dev_mode) {
|
||||
$map = $this->getCelerityResourceMap();
|
||||
$expect_hash = $map->getHashForName($path);
|
||||
|
||||
// Test if the URI hash is correct for our current resource map. If it
|
||||
// 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
|
||||
// shortly after a push.
|
||||
$is_cacheable = ($hash === $expect_hash) &&
|
||||
$this->isCacheableResourceType($type);
|
||||
if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) {
|
||||
// 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.
|
||||
return $this->makeResponseCacheable(new Aphront304Response());
|
||||
}
|
||||
|
||||
$is_cacheable = (!$dev_mode) &&
|
||||
$this->isCacheableResourceType($type);
|
||||
|
||||
$cache = null;
|
||||
$data = null;
|
||||
if ($is_cacheable) {
|
||||
if ($is_cacheable && !$dev_mode) {
|
||||
$cache = PhabricatorCaches::getImmutableCache();
|
||||
|
||||
$request_path = $this->getRequest()->getPath();
|
||||
|
@ -61,8 +70,6 @@ abstract class CelerityResourceController extends PhabricatorController {
|
|||
}
|
||||
|
||||
if ($data === null) {
|
||||
$map = $this->getCelerityResourceMap();
|
||||
|
||||
if ($map->isPackageResource($path)) {
|
||||
$resource_names = $map->getResourceNamesForPackageName($path);
|
||||
if (!$resource_names) {
|
||||
|
@ -117,7 +124,11 @@ abstract class CelerityResourceController extends PhabricatorController {
|
|||
$response->addAllowOrigin('*');
|
||||
}
|
||||
|
||||
return $this->makeResponseCacheable($response);
|
||||
if ($is_cacheable) {
|
||||
$response = $this->makeResponseCacheable($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function getSupportedResourceTypes() {
|
||||
|
|
|
@ -23,25 +23,8 @@ final class PhabricatorConduitConsoleController
|
|||
|
||||
$call_uri = '/api/'.$method->getAPIMethodName();
|
||||
|
||||
$status = $method->getMethodStatus();
|
||||
$reason = $method->getMethodStatusDescription();
|
||||
$errors = array();
|
||||
|
||||
switch ($status) {
|
||||
case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
|
||||
$reason = nonempty($reason, pht('This method is deprecated.'));
|
||||
$errors[] = pht('Deprecated Method: %s', $reason);
|
||||
break;
|
||||
case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
|
||||
$reason = nonempty(
|
||||
$reason,
|
||||
pht(
|
||||
'This method is new and unstable. Its interface is subject '.
|
||||
'to change.'));
|
||||
$errors[] = pht('Unstable Method: %s', $reason);
|
||||
break;
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setAction($call_uri)
|
||||
->setUser($request->getUser())
|
||||
|
@ -127,6 +110,41 @@ final class PhabricatorConduitConsoleController
|
|||
|
||||
$view = id(new PHUIPropertyListView());
|
||||
|
||||
$status = $method->getMethodStatus();
|
||||
$reason = $method->getMethodStatusDescription();
|
||||
|
||||
switch ($status) {
|
||||
case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
|
||||
$stability_icon = 'fa-exclamation-triangle yellow';
|
||||
$stability_label = pht('Unstable Method');
|
||||
$stability_info = nonempty(
|
||||
$reason,
|
||||
pht(
|
||||
'This method is new and unstable. Its interface is subject '.
|
||||
'to change.'));
|
||||
break;
|
||||
case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
|
||||
$stability_icon = 'fa-exclamation-triangle red';
|
||||
$stability_label = pht('Deprecated Method');
|
||||
$stability_info = nonempty($reason, pht('This method is deprecated.'));
|
||||
break;
|
||||
default:
|
||||
$stability_label = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($stability_label) {
|
||||
$view->addProperty(
|
||||
pht('Stability'),
|
||||
array(
|
||||
id(new PHUIIconView())->setIcon($stability_icon),
|
||||
' ',
|
||||
phutil_tag('strong', array(), $stability_label.':'),
|
||||
' ',
|
||||
$stability_info,
|
||||
));
|
||||
}
|
||||
|
||||
$view->addProperty(
|
||||
pht('Returns'),
|
||||
$method->getReturnType());
|
||||
|
|
|
@ -65,6 +65,7 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
|
|||
'cluster/' => array(
|
||||
'databases/' => 'PhabricatorConfigClusterDatabasesController',
|
||||
'notifications/' => 'PhabricatorConfigClusterNotificationsController',
|
||||
'repositories/' => 'PhabricatorConfigClusterRepositoriesController',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -102,14 +102,7 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck {
|
|||
$version = null;
|
||||
switch ($vcs['versionControlSystem']) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$bad_versions = array(
|
||||
'< 2.7.4' => pht(
|
||||
'Prior to 2.7.4, Git contains two remote code execution '.
|
||||
'vulnerabilities which allow an attacker to take control of a '.
|
||||
'system by crafting a commit which affects very long paths, '.
|
||||
'then pushing it or tricking a victim into fetching it. This '.
|
||||
'is a severe security vulnerability.'),
|
||||
);
|
||||
$bad_versions = array();
|
||||
list($err, $stdout, $stderr) = exec_manual('git --version');
|
||||
$version = trim(substr($stdout, strlen('git version ')));
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorConfigClusterRepositoriesController
|
||||
extends PhabricatorConfigController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$nav = $this->buildSideNavView();
|
||||
$nav->selectFilter('cluster/repositories/');
|
||||
|
||||
$title = pht('Repository Servers');
|
||||
|
||||
$crumbs = $this
|
||||
->buildApplicationCrumbs($nav)
|
||||
->addTextCrumb(pht('Repository Servers'));
|
||||
|
||||
$repository_status = $this->buildClusterRepositoryStatus();
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setNavigation($nav)
|
||||
->setMainColumn($repository_status);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
private function buildClusterRepositoryStatus() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
Javelin::initBehavior('phabricator-tooltips');
|
||||
|
||||
$all_services = id(new AlmanacServiceQuery())
|
||||
->setViewer($viewer)
|
||||
->withServiceTypes(
|
||||
array(
|
||||
AlmanacClusterRepositoryServiceType::SERVICETYPE,
|
||||
))
|
||||
->needBindings(true)
|
||||
->needProperties(true)
|
||||
->execute();
|
||||
$all_services = mpull($all_services, null, 'getPHID');
|
||||
|
||||
$all_repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->withHosted(PhabricatorRepositoryQuery::HOSTED_PHABRICATOR)
|
||||
->withTypes(
|
||||
array(
|
||||
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
|
||||
))
|
||||
->execute();
|
||||
$all_repositories = mpull($all_repositories, null, 'getPHID');
|
||||
|
||||
$all_versions = id(new PhabricatorRepositoryWorkingCopyVersion())
|
||||
->loadAll();
|
||||
|
||||
$all_devices = $this->getDevices($all_services, false);
|
||||
$all_active_devices = $this->getDevices($all_services, true);
|
||||
|
||||
$leader_versions = $this->getLeaderVersionsByRepository(
|
||||
$all_repositories,
|
||||
$all_versions,
|
||||
$all_active_devices);
|
||||
|
||||
$push_times = $this->loadLeaderPushTimes($leader_versions);
|
||||
|
||||
$repository_groups = mgroup($all_repositories, 'getAlmanacServicePHID');
|
||||
$repository_versions = mgroup($all_versions, 'getRepositoryPHID');
|
||||
|
||||
$rows = array();
|
||||
foreach ($all_services as $service) {
|
||||
$service_phid = $service->getPHID();
|
||||
|
||||
if ($service->getAlmanacPropertyValue('closed')) {
|
||||
$status_icon = 'fa-folder';
|
||||
$status_tip = pht('Closed');
|
||||
} else {
|
||||
$status_icon = 'fa-folder-open green';
|
||||
$status_tip = pht('Open');
|
||||
}
|
||||
|
||||
$status_icon = id(new PHUIIconView())
|
||||
->setIcon($status_icon)
|
||||
->addSigil('has-tooltip')
|
||||
->setMetadata(
|
||||
array(
|
||||
'tip' => $status_tip,
|
||||
));
|
||||
|
||||
$devices = idx($all_devices, $service_phid, array());
|
||||
$active_devices = idx($all_active_devices, $service_phid, array());
|
||||
|
||||
$device_icon = 'fa-server green';
|
||||
|
||||
$device_label = pht(
|
||||
'%s Active',
|
||||
phutil_count($active_devices));
|
||||
|
||||
$device_status = array(
|
||||
id(new PHUIIconView())->setIcon($device_icon),
|
||||
' ',
|
||||
$device_label,
|
||||
);
|
||||
|
||||
$repositories = idx($repository_groups, $service_phid, array());
|
||||
|
||||
$repository_status = pht(
|
||||
'%s',
|
||||
phutil_count($repositories));
|
||||
|
||||
$no_leader = array();
|
||||
$full_sync = array();
|
||||
$partial_sync = array();
|
||||
$no_sync = array();
|
||||
$lag = array();
|
||||
|
||||
// Threshold in seconds before we start complaining that repositories
|
||||
// are not synchronized when there is only one leader.
|
||||
$threshold = phutil_units('5 minutes in seconds');
|
||||
|
||||
$messages = array();
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$repository_phid = $repository->getPHID();
|
||||
|
||||
$leader_version = idx($leader_versions, $repository_phid);
|
||||
if ($leader_version === null) {
|
||||
$no_leader[] = $repository;
|
||||
$messages[] = pht(
|
||||
'Repository %s has an ambiguous leader.',
|
||||
$viewer->renderHandle($repository_phid)->render());
|
||||
continue;
|
||||
}
|
||||
|
||||
$versions = idx($repository_versions, $repository_phid, array());
|
||||
|
||||
$leaders = 0;
|
||||
foreach ($versions as $version) {
|
||||
if ($version->getRepositoryVersion() == $leader_version) {
|
||||
$leaders++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($leaders == count($active_devices)) {
|
||||
$full_sync[] = $repository;
|
||||
} else {
|
||||
$push_epoch = idx($push_times, $repository_phid);
|
||||
if ($push_epoch) {
|
||||
$duration = (PhabricatorTime::getNow() - $push_epoch);
|
||||
$lag[] = $duration;
|
||||
} else {
|
||||
$duration = null;
|
||||
}
|
||||
|
||||
if ($leaders >= 2 || ($duration && ($duration < $threshold))) {
|
||||
$partial_sync[] = $repository;
|
||||
} else {
|
||||
$no_sync[] = $repository;
|
||||
if ($push_epoch) {
|
||||
$messages[] = pht(
|
||||
'Repository %s has unreplicated changes (for %s).',
|
||||
$viewer->renderHandle($repository_phid)->render(),
|
||||
phutil_format_relative_time($duration));
|
||||
} else {
|
||||
$messages[] = pht(
|
||||
'Repository %s has unreplicated changes.',
|
||||
$viewer->renderHandle($repository_phid)->render());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$with_lag = false;
|
||||
|
||||
if ($no_leader) {
|
||||
$replication_icon = 'fa-times red';
|
||||
$replication_label = pht('Ambiguous Leader');
|
||||
} else if ($no_sync) {
|
||||
$replication_icon = 'fa-refresh yellow';
|
||||
$replication_label = pht('Unsynchronized');
|
||||
$with_lag = true;
|
||||
} else if ($partial_sync) {
|
||||
$replication_icon = 'fa-refresh green';
|
||||
$replication_label = pht('Partial');
|
||||
$with_lag = true;
|
||||
} else if ($full_sync) {
|
||||
$replication_icon = 'fa-check green';
|
||||
$replication_label = pht('Synchronized');
|
||||
} else {
|
||||
$replication_icon = 'fa-times grey';
|
||||
$replication_label = pht('No Repositories');
|
||||
}
|
||||
|
||||
if ($with_lag && $lag) {
|
||||
$lag_status = phutil_format_relative_time(max($lag));
|
||||
$lag_status = pht(' (%s)', $lag_status);
|
||||
} else {
|
||||
$lag_status = null;
|
||||
}
|
||||
|
||||
$replication_status = array(
|
||||
id(new PHUIIconView())->setIcon($replication_icon),
|
||||
' ',
|
||||
$replication_label,
|
||||
$lag_status,
|
||||
);
|
||||
|
||||
$messages = phutil_implode_html(phutil_tag('br'), $messages);
|
||||
|
||||
$rows[] = array(
|
||||
$status_icon,
|
||||
$viewer->renderHandle($service->getPHID()),
|
||||
$device_status,
|
||||
$repository_status,
|
||||
$replication_status,
|
||||
$messages,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setNoDataString(
|
||||
pht('No repository cluster services are configured.'))
|
||||
->setHeaders(
|
||||
array(
|
||||
null,
|
||||
pht('Service'),
|
||||
pht('Devices'),
|
||||
pht('Repos'),
|
||||
pht('Sync'),
|
||||
pht('Messages'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
null,
|
||||
'pri',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'wide',
|
||||
));
|
||||
|
||||
$doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Cluster Repository Status'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setIcon('fa-book')
|
||||
->setHref($doc_href)
|
||||
->setTag('a')
|
||||
->setText(pht('Documentation')));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
private function getDevices(
|
||||
array $all_services,
|
||||
$only_active) {
|
||||
|
||||
$devices = array();
|
||||
foreach ($all_services as $service) {
|
||||
$map = array();
|
||||
foreach ($service->getBindings() as $binding) {
|
||||
if ($only_active && $binding->getIsDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$device = $binding->getDevice();
|
||||
$device_phid = $device->getPHID();
|
||||
|
||||
$map[$device_phid] = $device;
|
||||
}
|
||||
$devices[$service->getPHID()] = $map;
|
||||
}
|
||||
|
||||
return $devices;
|
||||
}
|
||||
|
||||
private function getLeaderVersionsByRepository(
|
||||
array $all_repositories,
|
||||
array $all_versions,
|
||||
array $active_devices) {
|
||||
|
||||
$version_map = mgroup($all_versions, 'getRepositoryPHID');
|
||||
|
||||
$result = array();
|
||||
foreach ($all_repositories as $repository_phid => $repository) {
|
||||
$service_phid = $repository->getAlmanacServicePHID();
|
||||
if (!$service_phid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$devices = idx($active_devices, $service_phid);
|
||||
if (!$devices) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$versions = idx($version_map, $repository_phid, array());
|
||||
$versions = mpull($versions, null, 'getDevicePHID');
|
||||
$versions = array_select_keys($versions, array_keys($devices));
|
||||
if (!$versions) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$leader = (int)max(mpull($versions, 'getRepositoryVersion'));
|
||||
$result[$repository_phid] = $leader;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function loadLeaderPushTimes(array $leader_versions) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if (!$leader_versions) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$events = id(new PhabricatorRepositoryPushEventQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs($leader_versions)
|
||||
->execute();
|
||||
$events = mpull($events, null, 'getID');
|
||||
|
||||
$result = array();
|
||||
foreach ($leader_versions as $key => $version) {
|
||||
$event = idx($events, $version);
|
||||
if (!$event) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$key] = $event->getEpoch();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -25,6 +25,7 @@ abstract class PhabricatorConfigController extends PhabricatorController {
|
|||
$nav->addLabel(pht('Cluster'));
|
||||
$nav->addFilter('cluster/databases/', pht('Database Servers'));
|
||||
$nav->addFilter('cluster/notifications/', pht('Notification Servers'));
|
||||
$nav->addFilter('cluster/repositories/', pht('Repository Servers'));
|
||||
$nav->addLabel(pht('Welcome'));
|
||||
$nav->addFilter('welcome/', pht('Welcome Screen'));
|
||||
$nav->addLabel(pht('Modules'));
|
||||
|
|
|
@ -161,7 +161,7 @@ final class PhabricatorDashboardEditController
|
|||
|
||||
$form->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()));
|
||||
|
|
|
@ -240,7 +240,7 @@ final class PhabricatorDashboardPanelEditController
|
|||
|
||||
$form->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()));
|
||||
|
|
|
@ -8,7 +8,7 @@ final class DifferentialProjectsField
|
|||
}
|
||||
|
||||
public function getFieldName() {
|
||||
return pht('Projects');
|
||||
return pht('Tags');
|
||||
}
|
||||
|
||||
public function getFieldDescription() {
|
||||
|
@ -76,6 +76,7 @@ final class DifferentialProjectsField
|
|||
|
||||
public function getCommitMessageLabels() {
|
||||
return array(
|
||||
'Tags',
|
||||
'Project',
|
||||
'Projects',
|
||||
);
|
||||
|
|
|
@ -179,7 +179,7 @@ final class DifferentialRevisionSearchEngine
|
|||
->setValue($repository_phids))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setDatasource(new PhabricatorProjectLogicalDatasource())
|
||||
->setValue($projects))
|
||||
|
|
|
@ -55,8 +55,10 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
=> 'DiffusionCommitController',
|
||||
|
||||
'/diffusion/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
$this->getQueryRoutePattern()
|
||||
=> 'DiffusionRepositoryListController',
|
||||
$this->getEditRoutePattern('editpro/') =>
|
||||
'DiffusionRepositoryEditproController',
|
||||
'new/' => 'DiffusionRepositoryNewController',
|
||||
'(?P<edit>create)/' => 'DiffusionRepositoryCreateController',
|
||||
'(?P<edit>import)/' => 'DiffusionRepositoryCreateController',
|
||||
|
|
|
@ -131,7 +131,8 @@ final class DiffusionHistoryQueryConduitAPIMethod
|
|||
hgsprintf('reverse(ancestors(%s))', $commit_hash),
|
||||
$path_arg);
|
||||
|
||||
$stdout = PhabricatorRepository::filterMercurialDebugOutput($stdout);
|
||||
$stdout = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
|
||||
$stdout);
|
||||
$lines = explode("\n", trim($stdout));
|
||||
$lines = array_slice($lines, $offset);
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
|||
->executeOne();
|
||||
if ($repository) {
|
||||
$query->withRepository($repository);
|
||||
if ($bypass_cache) {
|
||||
$repository->synchronizeWorkingCopyBeforeRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,12 @@ abstract class DiffusionQueryConduitAPIMethod
|
|||
|
||||
$this->setDiffusionRequest($drequest);
|
||||
|
||||
// TODO: Allow web UI queries opt out of this if they don't care about
|
||||
// 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
|
||||
// date.
|
||||
$repository->synchronizeWorkingCopyBeforeRead();
|
||||
|
||||
return $this->getResult($request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryEditConduitAPIMethod
|
||||
extends PhabricatorEditEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'diffusion.repository.edit';
|
||||
}
|
||||
|
||||
public function newEditEngine() {
|
||||
return new DiffusionRepositoryEditEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht(
|
||||
'Apply transactions to create a new repository or edit an existing '.
|
||||
'one.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositorySearchConduitAPIMethod
|
||||
extends PhabricatorSearchEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'diffusion.repository.search';
|
||||
}
|
||||
|
||||
public function newSearchEngine() {
|
||||
return new PhabricatorRepositorySearchEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht('Read information about repositories.');
|
||||
}
|
||||
|
||||
}
|
|
@ -50,8 +50,7 @@ final class DiffusionPushEventViewController
|
|||
|
||||
$updates_table = id(new DiffusionPushLogListView())
|
||||
->setUser($viewer)
|
||||
->setLogs($logs)
|
||||
->setHandles($this->loadViewerHandles(mpull($logs, 'getPusherPHID')));
|
||||
->setLogs($logs);
|
||||
|
||||
$update_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('All Pushed Updates'))
|
||||
|
|
|
@ -162,9 +162,15 @@ final class DiffusionRepositoryCreateController
|
|||
|
||||
$activate = $form->getPage('done')
|
||||
->getControl('activate')->getValue();
|
||||
if ($activate == 'start') {
|
||||
$initial_status = PhabricatorRepository::STATUS_ACTIVE;
|
||||
} else {
|
||||
$initial_status = PhabricatorRepository::STATUS_INACTIVE;
|
||||
}
|
||||
|
||||
$xactions[] = id(clone $template)
|
||||
->setTransactionType($type_activate)
|
||||
->setNewValue(($activate == 'start'));
|
||||
->setNewValue($initial_status);
|
||||
|
||||
if ($service) {
|
||||
$xactions[] = id(clone $template)
|
||||
|
|
|
@ -16,12 +16,19 @@ final class DiffusionRepositoryEditActivateController
|
|||
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if (!$repository->isTracked()) {
|
||||
$new_status = PhabricatorRepository::STATUS_ACTIVE;
|
||||
} else {
|
||||
$new_status = PhabricatorRepository::STATUS_INACTIVE;
|
||||
}
|
||||
|
||||
$xaction = id(new PhabricatorRepositoryTransaction())
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ACTIVATE)
|
||||
->setNewValue(!$repository->isTracked());
|
||||
->setNewValue($new_status);
|
||||
|
||||
$editor = id(new PhabricatorRepositoryEditor())
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setActor($viewer)
|
||||
->applyTransactions($repository, array($xaction));
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryEditproController
|
||||
extends DiffusionRepositoryEditController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new DiffusionRepositoryEditEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -30,7 +30,8 @@ final class DiffusionRepositoryManageController
|
|||
foreach ($panels as $panel) {
|
||||
$panel
|
||||
->setViewer($viewer)
|
||||
->setRepository($repository);
|
||||
->setRepository($repository)
|
||||
->setController($this);
|
||||
}
|
||||
|
||||
$selected = $request->getURIData('panel');
|
||||
|
@ -63,10 +64,30 @@ final class DiffusionRepositoryManageController
|
|||
$repository->getPathURI('manage/'));
|
||||
$crumbs->addTextCrumb($panel->getManagementPanelLabel());
|
||||
|
||||
$header_text = pht(
|
||||
'%s: %s',
|
||||
$repository->getDisplayName(),
|
||||
$panel->getManagementPanelLabel());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($header_text)
|
||||
->setHeaderIcon('fa-pencil');
|
||||
if ($repository->isTracked()) {
|
||||
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
|
||||
} else {
|
||||
$header->setStatus('fa-ban', 'dark', pht('Inactive'));
|
||||
}
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setNavigation($nav)
|
||||
->setMainColumn($content);
|
||||
|
||||
$curtain = $panel->buildManagementPanelCurtain();
|
||||
if ($curtain) {
|
||||
$view->setCurtain($curtain);
|
||||
}
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
|
@ -95,5 +116,14 @@ final class DiffusionRepositoryManageController
|
|||
return $nav;
|
||||
}
|
||||
|
||||
public function newTimeline(PhabricatorRepository $repository) {
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$repository,
|
||||
new PhabricatorRepositoryTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -538,10 +538,35 @@ final class DiffusionServeController extends DiffusionController {
|
|||
$command = csprintf('%s', $bin);
|
||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$did_write_lock = false;
|
||||
if ($this->isReadOnlyRequest($repository)) {
|
||||
$repository->synchronizeWorkingCopyBeforeRead();
|
||||
} else {
|
||||
$did_write_lock = true;
|
||||
$repository->synchronizeWorkingCopyBeforeWrite($viewer);
|
||||
}
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
|
||||
->setEnv($env, true)
|
||||
->write($input)
|
||||
->resolve();
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
if ($did_write_lock) {
|
||||
$repository->synchronizeWorkingCopyAfterWrite();
|
||||
}
|
||||
|
||||
unset($unguarded);
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
}
|
||||
|
||||
if ($err) {
|
||||
if ($this->isValidGitShallowCloneResponse($stdout, $stderr)) {
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
const ENGINECONST = 'diffusion.repository';
|
||||
|
||||
public function isEngineConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEngineName() {
|
||||
return pht('Repositories');
|
||||
}
|
||||
|
||||
public function getSummaryHeader() {
|
||||
return pht('Edit Repositories');
|
||||
}
|
||||
|
||||
public function getSummaryText() {
|
||||
return pht('Creates and edits repositories.');
|
||||
}
|
||||
|
||||
public function getEngineApplicationClass() {
|
||||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
$viewer = $this->getViewer();
|
||||
return PhabricatorRepository::initializeNewRepository($viewer);
|
||||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new PhabricatorRepositoryQuery();
|
||||
}
|
||||
|
||||
protected function getObjectCreateTitleText($object) {
|
||||
return pht('Create Repository');
|
||||
}
|
||||
|
||||
protected function getObjectCreateButtonText($object) {
|
||||
return pht('Create Repository');
|
||||
}
|
||||
|
||||
protected function getObjectEditTitleText($object) {
|
||||
return pht('Edit Repository: %s', $object->getName());
|
||||
}
|
||||
|
||||
protected function getObjectEditShortText($object) {
|
||||
return $object->getDisplayName();
|
||||
}
|
||||
|
||||
protected function getObjectCreateShortText() {
|
||||
return pht('Create Repository');
|
||||
}
|
||||
|
||||
protected function getObjectName() {
|
||||
return pht('Repository');
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getPathURI('manage/');
|
||||
}
|
||||
|
||||
protected function getCreateNewObjectPolicy() {
|
||||
return $this->getApplication()->getPolicy(
|
||||
DiffusionCreateRepositoriesCapability::CAPABILITY);
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($viewer)
|
||||
->setObject($object)
|
||||
->execute();
|
||||
|
||||
return array(
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('vcs')
|
||||
->setLabel(pht('Version Control System'))
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_VCS)
|
||||
->setIsConduitOnly(true)
|
||||
->setIsCopyable(true)
|
||||
->setOptions(PhabricatorRepositoryType::getAllRepositoryTypes())
|
||||
->setDescription(pht('Underlying repository version control system.'))
|
||||
->setConduitDescription(
|
||||
pht(
|
||||
'Choose which version control system to use when creating a '.
|
||||
'repository.'))
|
||||
->setConduitTypeDescription(pht('Version control system selection.'))
|
||||
->setValue($object->getVersionControlSystem()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setIsRequired(true)
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_NAME)
|
||||
->setDescription(pht('The repository name.'))
|
||||
->setConduitDescription(pht('Rename the repository.'))
|
||||
->setConduitTypeDescription(pht('New repository name.'))
|
||||
->setValue($object->getName()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('callsign')
|
||||
->setLabel(pht('Callsign'))
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_CALLSIGN)
|
||||
->setDescription(pht('The repository callsign.'))
|
||||
->setConduitDescription(pht('Change the repository callsign.'))
|
||||
->setConduitTypeDescription(pht('New repository callsign.'))
|
||||
->setValue($object->getCallsign()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('shortName')
|
||||
->setLabel(pht('Short Name'))
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_SLUG)
|
||||
->setDescription(pht('Short, unique repository name.'))
|
||||
->setConduitDescription(pht('Change the repository short name.'))
|
||||
->setConduitTypeDescription(pht('New short name for the repository.'))
|
||||
->setValue($object->getRepositorySlug()),
|
||||
id(new PhabricatorRemarkupEditField())
|
||||
->setKey('description')
|
||||
->setLabel(pht('Description'))
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_DESCRIPTION)
|
||||
->setDescription(pht('Repository description.'))
|
||||
->setConduitDescription(pht('Change the repository description.'))
|
||||
->setConduitTypeDescription(pht('New repository description.'))
|
||||
->setValue($object->getDetail('description')),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('encoding')
|
||||
->setLabel(pht('Text Encoding'))
|
||||
->setIsCopyable(true)
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENCODING)
|
||||
->setDescription(pht('Default text encoding.'))
|
||||
->setConduitDescription(pht('Change the default text encoding.'))
|
||||
->setConduitTypeDescription(pht('New text encoding.'))
|
||||
->setValue($object->getDetail('encoding')),
|
||||
id(new PhabricatorSelectEditField())
|
||||
->setKey('status')
|
||||
->setLabel(pht('Status'))
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ACTIVATE)
|
||||
->setIsConduitOnly(true)
|
||||
->setOptions(PhabricatorRepository::getStatusNameMap())
|
||||
->setDescription(pht('Active or inactive status.'))
|
||||
->setConduitDescription(pht('Active or deactivate the repository.'))
|
||||
->setConduitTypeDescription(pht('New repository status.'))
|
||||
->setValue($object->getStatus()),
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('defaultBranch')
|
||||
->setLabel(pht('Default Branch'))
|
||||
->setTransactionType(
|
||||
PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH)
|
||||
->setIsCopyable(true)
|
||||
->setDescription(pht('Default branch name.'))
|
||||
->setConduitDescription(pht('Set the default branch name.'))
|
||||
->setConduitTypeDescription(pht('New default branch name.'))
|
||||
->setValue($object->getDetail('default-branch')),
|
||||
id(new PhabricatorPolicyEditField())
|
||||
->setKey('policy.push')
|
||||
->setLabel(pht('Push Policy'))
|
||||
->setAliases(array('push'))
|
||||
->setIsCopyable(true)
|
||||
->setCapability(DiffusionPushCapability::CAPABILITY)
|
||||
->setPolicies($policies)
|
||||
->setTransactionType(PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY)
|
||||
->setDescription(
|
||||
pht('Controls who can push changes to the repository.'))
|
||||
->setConduitDescription(
|
||||
pht('Change the push policy of the repository.'))
|
||||
->setConduitTypeDescription(pht('New policy PHID or constant.'))
|
||||
->setValue($object->getPolicy(DiffusionPushCapability::CAPABILITY)),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1058,8 +1058,16 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
// up.
|
||||
$phid = id(new PhabricatorRepositoryPushLog())->generatePHID();
|
||||
|
||||
$device = AlmanacKeys::getLiveDevice();
|
||||
if ($device) {
|
||||
$device_phid = $device->getPHID();
|
||||
} else {
|
||||
$device_phid = null;
|
||||
}
|
||||
|
||||
return PhabricatorRepositoryPushLog::initializeNewLog($this->getViewer())
|
||||
->setPHID($phid)
|
||||
->setDevicePHID($device_phid)
|
||||
->setRepositoryPHID($this->getRepository()->getPHID())
|
||||
->attachRepository($this->getRepository())
|
||||
->setEpoch(time());
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryBasicsManagementPanel
|
||||
extends DiffusionRepositoryManagementPanel {
|
||||
|
||||
const PANELKEY = 'basics';
|
||||
|
||||
public function getManagementPanelLabel() {
|
||||
return pht('Basics');
|
||||
}
|
||||
|
||||
public function getManagementPanelOrder() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
protected function buildManagementPanelActions() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$repository,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$edit_uri = $repository->getPathURI('manage/');
|
||||
$activate_uri = $repository->getPathURI('edit/activate/');
|
||||
$delete_uri = $repository->getPathURI('edit/delete/');
|
||||
$encoding_uri = $repository->getPathURI('edit/encoding/');
|
||||
|
||||
if ($repository->isTracked()) {
|
||||
$activate_icon = 'fa-pause';
|
||||
$activate_label = pht('Deactivate Repository');
|
||||
} else {
|
||||
$activate_icon = 'fa-play';
|
||||
$activate_label = pht('Activate Repository');
|
||||
}
|
||||
|
||||
return array(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil')
|
||||
->setName(pht('Edit Basic Information'))
|
||||
->setHref($edit_uri)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit),
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-text-width')
|
||||
->setName(pht('Edit Text Encoding'))
|
||||
->setHref($encoding_uri)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit),
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($activate_uri)
|
||||
->setIcon($activate_icon)
|
||||
->setName($activate_label)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true),
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Delete Repository'))
|
||||
->setIcon('fa-times')
|
||||
->setHref($delete_uri)
|
||||
->setDisabled(true)
|
||||
->setWorkflow(true),
|
||||
);
|
||||
}
|
||||
|
||||
public function buildManagementPanelContent() {
|
||||
$result = array();
|
||||
|
||||
$result[] = $this->newBox(pht('Repository Basics'), $this->buildBasics());
|
||||
|
||||
$description = $this->buildDescription();
|
||||
if ($description) {
|
||||
$result[] = $this->newBox(pht('Description'), $description);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function buildBasics() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setViewer($viewer)
|
||||
->setActionList($this->newActions());
|
||||
|
||||
$name = $repository->getName();
|
||||
$view->addProperty(pht('Name'), $name);
|
||||
|
||||
$type = PhabricatorRepositoryType::getNameForRepositoryType(
|
||||
$repository->getVersionControlSystem());
|
||||
$view->addProperty(pht('Type'), $type);
|
||||
|
||||
$callsign = $repository->getCallsign();
|
||||
if (!strlen($callsign)) {
|
||||
$callsign = phutil_tag('em', array(), pht('No Callsign'));
|
||||
}
|
||||
$view->addProperty(pht('Callsign'), $callsign);
|
||||
|
||||
$short_name = $repository->getRepositorySlug();
|
||||
if ($short_name === null) {
|
||||
$short_name = $repository->getCloneName();
|
||||
$short_name = phutil_tag('em', array(), $short_name);
|
||||
}
|
||||
$view->addProperty(pht('Short Name'), $short_name);
|
||||
|
||||
$encoding = $repository->getDetail('encoding');
|
||||
if (!$encoding) {
|
||||
$encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)'));
|
||||
}
|
||||
$view->addProperty(pht('Encoding'), $encoding);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
|
||||
private function buildDescription() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$description = $repository->getDetail('description');
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setViewer($viewer);
|
||||
if (!strlen($description)) {
|
||||
$description = phutil_tag('em', array(), pht('No description provided.'));
|
||||
} else {
|
||||
$description = new PHUIRemarkupView($viewer, $description);
|
||||
}
|
||||
$view->addTextContent($description);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@ final class DiffusionRepositoryClusterManagementPanel
|
|||
}
|
||||
|
||||
public function getManagementPanelOrder() {
|
||||
return 12345;
|
||||
return 600;
|
||||
}
|
||||
|
||||
public function buildManagementPanelContent() {
|
||||
|
@ -104,6 +104,29 @@ final class DiffusionRepositoryClusterManagementPanel
|
|||
->setIcon('fa-pencil grey');
|
||||
}
|
||||
|
||||
$write_properties = null;
|
||||
if ($version) {
|
||||
$write_properties = $version->getWriteProperties();
|
||||
if ($write_properties) {
|
||||
try {
|
||||
$write_properties = phutil_json_decode($write_properties);
|
||||
} catch (Exception $ex) {
|
||||
$write_properties = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($write_properties) {
|
||||
$writer_phid = idx($write_properties, 'userPHID');
|
||||
$last_writer = $viewer->renderHandle($writer_phid);
|
||||
|
||||
$writer_epoch = idx($write_properties, 'epoch');
|
||||
$writer_epoch = phabricator_datetime($writer_epoch, $viewer);
|
||||
} else {
|
||||
$last_writer = null;
|
||||
$writer_epoch = null;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
$binding_icon,
|
||||
phutil_tag(
|
||||
|
@ -114,6 +137,8 @@ final class DiffusionRepositoryClusterManagementPanel
|
|||
$device->getName()),
|
||||
$version_number,
|
||||
$is_writing,
|
||||
$last_writer,
|
||||
$writer_epoch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +151,8 @@ final class DiffusionRepositoryClusterManagementPanel
|
|||
pht('Device'),
|
||||
pht('Version'),
|
||||
pht('Writing'),
|
||||
pht('Last Writer'),
|
||||
pht('Last Write At'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
|
@ -133,6 +160,8 @@ final class DiffusionRepositoryClusterManagementPanel
|
|||
null,
|
||||
null,
|
||||
'right wide',
|
||||
null,
|
||||
'date',
|
||||
));
|
||||
|
||||
$doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
|
||||
|
@ -160,6 +189,7 @@ final class DiffusionRepositoryClusterManagementPanel
|
|||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryHistoryManagementPanel
|
||||
extends DiffusionRepositoryManagementPanel {
|
||||
|
||||
const PANELKEY = 'history';
|
||||
|
||||
public function getManagementPanelLabel() {
|
||||
return pht('History');
|
||||
}
|
||||
|
||||
public function getManagementPanelOrder() {
|
||||
return 900;
|
||||
}
|
||||
|
||||
public function buildManagementPanelContent() {
|
||||
return $this->newTimeline();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ abstract class DiffusionRepositoryManagementPanel
|
|||
|
||||
private $viewer;
|
||||
private $repository;
|
||||
private $controller;
|
||||
|
||||
final public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
|
@ -24,6 +25,11 @@ abstract class DiffusionRepositoryManagementPanel
|
|||
return $this->repository;
|
||||
}
|
||||
|
||||
final public function setController(PhabricatorController $controller) {
|
||||
$this->controller = $controller;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getManagementPanelKey() {
|
||||
return $this->getPhobjectClassConstant('PANELKEY');
|
||||
}
|
||||
|
@ -32,6 +38,47 @@ abstract class DiffusionRepositoryManagementPanel
|
|||
abstract public function getManagementPanelOrder();
|
||||
abstract public function buildManagementPanelContent();
|
||||
|
||||
protected function buildManagementPanelActions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
final protected function newActions() {
|
||||
$actions = $this->buildManagementPanelActions();
|
||||
if (!$actions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$action_list = id(new PhabricatorActionListView())
|
||||
->setViewer($viewer);
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$action_list->addAction($action);
|
||||
}
|
||||
|
||||
return $action_list;
|
||||
}
|
||||
|
||||
public function buildManagementPanelCurtain() {
|
||||
// TODO: Delete or fix this, curtains always render in the left gutter
|
||||
// at the moment.
|
||||
return null;
|
||||
|
||||
$actions = $this->newActions();
|
||||
if (!$actions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$curtain = id(new PHUICurtainView())
|
||||
->setViewer($viewer)
|
||||
->setActionList($actions);
|
||||
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
public static function getAllPanels() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
|
@ -40,4 +87,15 @@ abstract class DiffusionRepositoryManagementPanel
|
|||
->execute();
|
||||
}
|
||||
|
||||
final protected function newBox($header_text, $body) {
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText($header_text)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($body);
|
||||
}
|
||||
|
||||
final protected function newTimeline() {
|
||||
return $this->controller->newTimeline($this->getRepository());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryPoliciesManagementPanel
|
||||
extends DiffusionRepositoryManagementPanel {
|
||||
|
||||
const PANELKEY = 'policies';
|
||||
|
||||
public function getManagementPanelLabel() {
|
||||
return pht('Policies');
|
||||
}
|
||||
|
||||
public function getManagementPanelOrder() {
|
||||
return 300;
|
||||
}
|
||||
|
||||
protected function buildManagementPanelActions() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$repository,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$edit_uri = $repository->getPathURI('manage/');
|
||||
|
||||
return array(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil')
|
||||
->setName(pht('Edit Policies'))
|
||||
->setHref($edit_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());
|
||||
|
||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
||||
$viewer,
|
||||
$repository);
|
||||
|
||||
$view_parts = array();
|
||||
if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
|
||||
$space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
|
||||
$repository);
|
||||
$view_parts[] = $viewer->renderHandle($space_phid);
|
||||
}
|
||||
$view_parts[] = $descriptions[PhabricatorPolicyCapability::CAN_VIEW];
|
||||
|
||||
$view->addProperty(
|
||||
pht('Visible To'),
|
||||
phutil_implode_html(" \xC2\xB7 ", $view_parts));
|
||||
|
||||
$view->addProperty(
|
||||
pht('Editable By'),
|
||||
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
|
||||
|
||||
$pushable = $repository->isHosted()
|
||||
? $descriptions[DiffusionPushCapability::CAPABILITY]
|
||||
: phutil_tag('em', array(), pht('Not a Hosted Repository'));
|
||||
$view->addProperty(pht('Pushable By'), $pushable);
|
||||
|
||||
return $this->newBox(pht('Policies'), $view);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,457 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryStatusManagementPanel
|
||||
extends DiffusionRepositoryManagementPanel {
|
||||
|
||||
const PANELKEY = 'status';
|
||||
|
||||
public function getManagementPanelLabel() {
|
||||
return pht('Status');
|
||||
}
|
||||
|
||||
public function getManagementPanelOrder() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
protected function buildManagementPanelActions() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$repository,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$update_uri = $repository->getPathURI('edit/update/');
|
||||
|
||||
return array(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-refresh')
|
||||
->setName(pht('Update Now'))
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_edit)
|
||||
->setHref($update_uri),
|
||||
);
|
||||
}
|
||||
|
||||
public function buildManagementPanelContent() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setViewer($viewer)
|
||||
->setActionList($this->newActions());
|
||||
|
||||
$view->addProperty(
|
||||
pht('Update Frequency'),
|
||||
$this->buildRepositoryUpdateInterval($repository));
|
||||
|
||||
|
||||
list($status, $raw_error) = $this->buildRepositoryStatus($repository);
|
||||
|
||||
$view->addProperty(pht('Status'), $status);
|
||||
if ($raw_error) {
|
||||
$view->addSectionHeader(pht('Raw Error'));
|
||||
$view->addTextContent($raw_error);
|
||||
}
|
||||
|
||||
return $this->newBox(pht('Status'), $view);
|
||||
}
|
||||
|
||||
private function buildRepositoryUpdateInterval(
|
||||
PhabricatorRepository $repository) {
|
||||
|
||||
$smart_wait = $repository->loadUpdateInterval();
|
||||
|
||||
$doc_href = PhabricatorEnv::getDoclink(
|
||||
'Diffusion User Guide: Repository Updates');
|
||||
|
||||
return array(
|
||||
phutil_format_relative_time_detailed($smart_wait),
|
||||
" \xC2\xB7 ",
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $doc_href,
|
||||
'target' => '_blank',
|
||||
),
|
||||
pht('Learn More')),
|
||||
);
|
||||
}
|
||||
|
||||
private function buildRepositoryStatus(
|
||||
PhabricatorRepository $repository) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$is_cluster = $repository->getAlmanacServicePHID();
|
||||
|
||||
$view = new PHUIStatusListView();
|
||||
|
||||
$messages = id(new PhabricatorRepositoryStatusMessage())
|
||||
->loadAllWhere('repositoryID = %d', $repository->getID());
|
||||
$messages = mpull($messages, null, 'getStatusType');
|
||||
|
||||
if ($repository->isTracked()) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Repository Active')));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey')
|
||||
->setTarget(pht('Repository Inactive'))
|
||||
->setNote(
|
||||
pht('Activate this repository to begin or resume import.')));
|
||||
return $view;
|
||||
}
|
||||
|
||||
$binaries = array();
|
||||
$svnlook_check = false;
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$binaries[] = 'git';
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
$binaries[] = 'svn';
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$binaries[] = 'hg';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($repository->isHosted()) {
|
||||
if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) {
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$binaries[] = 'git-http-backend';
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
$binaries[] = 'svnserve';
|
||||
$binaries[] = 'svnadmin';
|
||||
$binaries[] = 'svnlook';
|
||||
$svnlook_check = true;
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$binaries[] = 'hg';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) {
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$binaries[] = 'git-receive-pack';
|
||||
$binaries[] = 'git-upload-pack';
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
$binaries[] = 'svnserve';
|
||||
$binaries[] = 'svnadmin';
|
||||
$binaries[] = 'svnlook';
|
||||
$svnlook_check = true;
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$binaries[] = 'hg';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$binaries = array_unique($binaries);
|
||||
if (!$is_cluster) {
|
||||
// We're only checking for binaries if we aren't running with a cluster
|
||||
// configuration. In theory, we could check for binaries on the
|
||||
// repository host machine, but we'd need to make this more complicated
|
||||
// to do that.
|
||||
|
||||
foreach ($binaries as $binary) {
|
||||
$where = Filesystem::resolveBinary($binary);
|
||||
if (!$where) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(
|
||||
pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))
|
||||
->setNote(pht(
|
||||
"Unable to find this binary in the webserver's PATH. You may ".
|
||||
"need to configure %s.",
|
||||
$this->getEnvConfigLink())));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(
|
||||
pht('Found Binary %s', phutil_tag('tt', array(), $binary)))
|
||||
->setNote(phutil_tag('tt', array(), $where)));
|
||||
}
|
||||
}
|
||||
|
||||
// This gets checked generically above. However, for svn commit hooks, we
|
||||
// need this to be in environment.append-paths because subversion strips
|
||||
// PATH.
|
||||
if ($svnlook_check) {
|
||||
$where = Filesystem::resolveBinary('svnlook');
|
||||
if ($where) {
|
||||
$path = substr($where, 0, strlen($where) - strlen('svnlook'));
|
||||
$dirs = PhabricatorEnv::getEnvConfig('environment.append-paths');
|
||||
$in_path = false;
|
||||
foreach ($dirs as $dir) {
|
||||
if (Filesystem::isDescendant($path, $dir)) {
|
||||
$in_path = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$in_path) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(
|
||||
pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))
|
||||
->setNote(pht(
|
||||
'Unable to find this binary in `%s`. '.
|
||||
'You need to configure %s and include %s.',
|
||||
'environment.append-paths',
|
||||
$this->getEnvConfigLink(),
|
||||
$path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd');
|
||||
|
||||
$daemon_instructions = pht(
|
||||
'Use %s to start daemons. See %s.',
|
||||
phutil_tag('tt', array(), 'bin/phd start'),
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $doc_href,
|
||||
),
|
||||
pht('Managing Daemons with phd')));
|
||||
|
||||
|
||||
$pull_daemon = id(new PhabricatorDaemonLogQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
|
||||
->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon'))
|
||||
->setLimit(1)
|
||||
->execute();
|
||||
|
||||
if ($pull_daemon) {
|
||||
|
||||
// TODO: In a cluster environment, we need a daemon on this repository's
|
||||
// host, specifically, and we aren't checking for that right now. This
|
||||
// is a reasonable proxy for things being more-or-less correctly set up,
|
||||
// though.
|
||||
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Pull Daemon Running')));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('Pull Daemon Not Running'))
|
||||
->setNote($daemon_instructions));
|
||||
}
|
||||
|
||||
|
||||
$task_daemon = id(new PhabricatorDaemonLogQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
|
||||
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
|
||||
->setLimit(1)
|
||||
->execute();
|
||||
if ($task_daemon) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Task Daemon Running')));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('Task Daemon Not Running'))
|
||||
->setNote($daemon_instructions));
|
||||
}
|
||||
|
||||
|
||||
if ($is_cluster) {
|
||||
// Just omit this status check for now in cluster environments. We
|
||||
// could make a service call and pull it from the repository host
|
||||
// eventually.
|
||||
} else if ($repository->usesLocalWorkingCopy()) {
|
||||
$local_parent = dirname($repository->getLocalPath());
|
||||
if (Filesystem::pathExists($local_parent)) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Storage Directory OK'))
|
||||
->setNote(phutil_tag('tt', array(), $local_parent)));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('No Storage Directory'))
|
||||
->setNote(
|
||||
pht(
|
||||
'Storage directory %s does not exist, or is not readable by '.
|
||||
'the webserver. Create this directory or make it readable.',
|
||||
phutil_tag('tt', array(), $local_parent))));
|
||||
return $view;
|
||||
}
|
||||
|
||||
$local_path = $repository->getLocalPath();
|
||||
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT);
|
||||
if ($message) {
|
||||
switch ($message->getStatusCode()) {
|
||||
case PhabricatorRepositoryStatusMessage::CODE_ERROR:
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('Initialization Error'))
|
||||
->setNote($message->getParameter('message')));
|
||||
return $view;
|
||||
case PhabricatorRepositoryStatusMessage::CODE_OKAY:
|
||||
if (Filesystem::pathExists($local_path)) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Working Copy OK'))
|
||||
->setNote(phutil_tag('tt', array(), $local_path)));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('Working Copy Error'))
|
||||
->setNote(
|
||||
pht(
|
||||
'Working copy %s has been deleted, or is not '.
|
||||
'readable by the webserver. Make this directory '.
|
||||
'readable. If it has been deleted, the daemons should '.
|
||||
'restore it automatically.',
|
||||
phutil_tag('tt', array(), $local_path))));
|
||||
return $view;
|
||||
}
|
||||
break;
|
||||
case PhabricatorRepositoryStatusMessage::CODE_WORKING:
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')
|
||||
->setTarget(pht('Initializing Working Copy'))
|
||||
->setNote(pht('Daemons are initializing the working copy.')));
|
||||
return $view;
|
||||
default:
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('Unknown Init Status'))
|
||||
->setNote($message->getStatusCode()));
|
||||
return $view;
|
||||
}
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')
|
||||
->setTarget(pht('No Working Copy Yet'))
|
||||
->setNote(
|
||||
pht('Waiting for daemons to build a working copy.')));
|
||||
return $view;
|
||||
}
|
||||
}
|
||||
|
||||
$raw_error = null;
|
||||
|
||||
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
|
||||
if ($message) {
|
||||
switch ($message->getStatusCode()) {
|
||||
case PhabricatorRepositoryStatusMessage::CODE_ERROR:
|
||||
$message = $message->getParameter('message');
|
||||
|
||||
$suggestion = null;
|
||||
if (preg_match('/Permission denied \(publickey\)./', $message)) {
|
||||
$suggestion = pht(
|
||||
'Public Key Error: This error usually indicates that the '.
|
||||
'keypair you have configured does not have permission to '.
|
||||
'access the repository.');
|
||||
}
|
||||
|
||||
$raw_error = $message;
|
||||
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
|
||||
->setTarget(pht('Update Error'))
|
||||
->setNote($suggestion));
|
||||
return $view;
|
||||
case PhabricatorRepositoryStatusMessage::CODE_OKAY:
|
||||
$ago = (PhabricatorTime::getNow() - $message->getEpoch());
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Updates OK'))
|
||||
->setNote(
|
||||
pht(
|
||||
'Last updated %s (%s ago).',
|
||||
phabricator_datetime($message->getEpoch(), $viewer),
|
||||
phutil_format_relative_time_detailed($ago))));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')
|
||||
->setTarget(pht('Waiting For Update'))
|
||||
->setNote(
|
||||
pht('Waiting for daemons to read updates.')));
|
||||
}
|
||||
|
||||
if ($repository->isImporting()) {
|
||||
$ratio = $repository->loadImportProgress();
|
||||
$percentage = sprintf('%.2f%%', 100 * $ratio);
|
||||
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')
|
||||
->setTarget(pht('Importing'))
|
||||
->setNote(
|
||||
pht('%s Complete', $percentage)));
|
||||
} else {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
|
||||
->setTarget(pht('Fully Imported')));
|
||||
}
|
||||
|
||||
if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) {
|
||||
$view->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setIcon(PHUIStatusItemView::ICON_UP, 'indigo')
|
||||
->setTarget(pht('Prioritized'))
|
||||
->setNote(pht('This repository will be updated soon!')));
|
||||
}
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$repository,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
if ($raw_error !== null) {
|
||||
if (!$can_edit) {
|
||||
$raw_message = pht(
|
||||
'You must be able to edit a repository to see raw error messages '.
|
||||
'because they sometimes disclose sensitive information.');
|
||||
$raw_message = phutil_tag('em', array(), $raw_message);
|
||||
} else {
|
||||
$raw_message = phutil_escape_html_newlines($raw_error);
|
||||
}
|
||||
} else {
|
||||
$raw_message = null;
|
||||
}
|
||||
|
||||
return array($view, $raw_message);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryURIsManagementPanel
|
||||
extends DiffusionRepositoryManagementPanel {
|
||||
|
||||
const PANELKEY = 'uris';
|
||||
|
||||
public function getManagementPanelLabel() {
|
||||
return pht('Clone / Fetch / Mirror');
|
||||
}
|
||||
|
||||
public function getManagementPanelOrder() {
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function buildManagementPanelContent() {
|
||||
$repository = $this->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$repository->attachURIs(array());
|
||||
$uris = $repository->getURIs();
|
||||
|
||||
Javelin::initBehavior('phabricator-tooltips');
|
||||
$rows = array();
|
||||
foreach ($uris as $uri) {
|
||||
|
||||
$uri_name = $uri->getDisplayURI();
|
||||
|
||||
if ($uri->getIsDisabled()) {
|
||||
$status_icon = 'fa-times grey';
|
||||
} else {
|
||||
$status_icon = 'fa-check green';
|
||||
}
|
||||
|
||||
$uri_status = id(new PHUIIconView())->setIcon($status_icon);
|
||||
|
||||
switch ($uri->getEffectiveIOType()) {
|
||||
case PhabricatorRepositoryURI::IO_OBSERVE:
|
||||
$io_icon = 'fa-download green';
|
||||
$io_label = pht('Observe');
|
||||
break;
|
||||
case PhabricatorRepositoryURI::IO_MIRROR:
|
||||
$io_icon = 'fa-upload green';
|
||||
$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(
|
||||
id(new PHUIIconView())->setIcon($io_icon),
|
||||
' ',
|
||||
$io_label,
|
||||
);
|
||||
|
||||
switch ($uri->getEffectiveDisplayType()) {
|
||||
case PhabricatorRepositoryURI::DISPLAY_NEVER:
|
||||
$display_icon = 'fa-eye-slash grey';
|
||||
$display_label = pht('Hidden');
|
||||
break;
|
||||
case PhabricatorRepositoryURI::DISPLAY_ALWAYS:
|
||||
$display_icon = 'fa-eye green';
|
||||
$display_label = pht('Visible');
|
||||
break;
|
||||
}
|
||||
|
||||
$uri_display = array(
|
||||
id(new PHUIIconView())->setIcon($display_icon),
|
||||
' ',
|
||||
$display_label,
|
||||
);
|
||||
|
||||
$rows[] = array(
|
||||
$uri_status,
|
||||
$uri_name,
|
||||
$uri_io,
|
||||
$uri_display,
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setNoDataString(pht('This repository has no URIs.'))
|
||||
->setHeaders(
|
||||
array(
|
||||
null,
|
||||
pht('URI'),
|
||||
pht('I/O'),
|
||||
pht('Display'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
null,
|
||||
'pri wide',
|
||||
null,
|
||||
null,
|
||||
));
|
||||
|
||||
$doc_href = PhabricatorEnv::getDoclink(
|
||||
'Diffusion User Guide: Repository URIs');
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Repository URIs'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setIcon('fa-book')
|
||||
->setHref($doc_href)
|
||||
->setTag('a')
|
||||
->setText(pht('Documentation')));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
}
|
233
src/applications/diffusion/protocol/DiffusionCommandEngine.php
Normal file
233
src/applications/diffusion/protocol/DiffusionCommandEngine.php
Normal file
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
abstract class DiffusionCommandEngine extends Phobject {
|
||||
|
||||
private $repository;
|
||||
private $protocol;
|
||||
private $credentialPHID;
|
||||
private $argv;
|
||||
private $passthru;
|
||||
private $connectAsDevice;
|
||||
private $sudoAsDaemon;
|
||||
|
||||
public static function newCommandEngine(PhabricatorRepository $repository) {
|
||||
$engines = self::newCommandEngines();
|
||||
|
||||
foreach ($engines as $engine) {
|
||||
if ($engine->canBuildForRepository($repository)) {
|
||||
return id(clone $engine)
|
||||
->setRepository($repository);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No registered command engine can build commands for this '.
|
||||
'repository ("%s").',
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
private static function newCommandEngines() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->execute();
|
||||
}
|
||||
|
||||
abstract protected function canBuildForRepository(
|
||||
PhabricatorRepository $repository);
|
||||
|
||||
abstract protected function newFormattedCommand($pattern, array $argv);
|
||||
abstract protected function newCustomEnvironment();
|
||||
|
||||
public function setRepository(PhabricatorRepository $repository) {
|
||||
$this->repository = $repository;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepository() {
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
public function setProtocol($protocol) {
|
||||
$this->protocol = $protocol;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProtocol() {
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
public function setCredentialPHID($credential_phid) {
|
||||
$this->credentialPHID = $credential_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCredentialPHID() {
|
||||
return $this->credentialPHID;
|
||||
}
|
||||
|
||||
public function setArgv(array $argv) {
|
||||
$this->argv = $argv;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getArgv() {
|
||||
return $this->argv;
|
||||
}
|
||||
|
||||
public function setPassthru($passthru) {
|
||||
$this->passthru = $passthru;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPassthru() {
|
||||
return $this->passthru;
|
||||
}
|
||||
|
||||
public function setConnectAsDevice($connect_as_device) {
|
||||
$this->connectAsDevice = $connect_as_device;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConnectAsDevice() {
|
||||
return $this->connectAsDevice;
|
||||
}
|
||||
|
||||
public function setSudoAsDaemon($sudo_as_daemon) {
|
||||
$this->sudoAsDaemon = $sudo_as_daemon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSudoAsDaemon() {
|
||||
return $this->sudoAsDaemon;
|
||||
}
|
||||
|
||||
public function newFuture() {
|
||||
$argv = $this->newCommandArgv();
|
||||
$env = $this->newCommandEnvironment();
|
||||
|
||||
if ($this->getSudoAsDaemon()) {
|
||||
$command = call_user_func_array('csprintf', $argv);
|
||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||
$argv = array('%C', $command);
|
||||
}
|
||||
|
||||
if ($this->getPassthru()) {
|
||||
$future = newv('PhutilExecPassthru', $argv);
|
||||
} else {
|
||||
$future = newv('ExecFuture', $argv);
|
||||
}
|
||||
|
||||
$future->setEnv($env);
|
||||
|
||||
return $future;
|
||||
}
|
||||
|
||||
private function newCommandArgv() {
|
||||
$argv = $this->argv;
|
||||
$pattern = $argv[0];
|
||||
$argv = array_slice($argv, 1);
|
||||
|
||||
list($pattern, $argv) = $this->newFormattedCommand($pattern, $argv);
|
||||
|
||||
return array_merge(array($pattern), $argv);
|
||||
}
|
||||
|
||||
private function newCommandEnvironment() {
|
||||
$env = $this->newCommonEnvironment() + $this->newCustomEnvironment();
|
||||
foreach ($env as $key => $value) {
|
||||
if ($value === null) {
|
||||
unset($env[$key]);
|
||||
}
|
||||
}
|
||||
return $env;
|
||||
}
|
||||
|
||||
private function newCommonEnvironment() {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$env = array();
|
||||
// NOTE: Force the language to "en_US.UTF-8", which overrides locale
|
||||
// settings. This makes stuff print in English instead of, e.g., French,
|
||||
// so we can parse the output of some commands, error messages, etc.
|
||||
$env['LANG'] = 'en_US.UTF-8';
|
||||
|
||||
// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
|
||||
$env['PHABRICATOR_ENV'] = PhabricatorEnv::getSelectedEnvironmentName();
|
||||
|
||||
$as_device = $this->getConnectAsDevice();
|
||||
$credential_phid = $this->getCredentialPHID();
|
||||
|
||||
if ($as_device) {
|
||||
$device = AlmanacKeys::getLiveDevice();
|
||||
if (!$device) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Attempting to build a reposiory command (for repository "%s") '.
|
||||
'as device, but this host ("%s") is not configured as a cluster '.
|
||||
'device.',
|
||||
$repository->getDisplayName(),
|
||||
php_uname('n')));
|
||||
}
|
||||
|
||||
if ($credential_phid) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Attempting to build a repository command (for repository "%s"), '.
|
||||
'but the CommandEngine is configured to connect as both the '.
|
||||
'current cluster device ("%s") and with a specific credential '.
|
||||
'("%s"). These options are mutually exclusive. Connections must '.
|
||||
'authenticate as one or the other, not both.',
|
||||
$repository->getDisplayName(),
|
||||
$device->getName(),
|
||||
$credential_phid));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->isAnySSHProtocol()) {
|
||||
if ($credential_phid) {
|
||||
$env['PHABRICATOR_CREDENTIAL'] = $credential_phid;
|
||||
}
|
||||
if ($as_device) {
|
||||
$env['PHABRICATOR_AS_DEVICE'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
protected function isSSHProtocol() {
|
||||
return ($this->getProtocol() == 'ssh');
|
||||
}
|
||||
|
||||
protected function isSVNProtocol() {
|
||||
return ($this->getProtocol() == 'svn');
|
||||
}
|
||||
|
||||
protected function isSVNSSHProtocol() {
|
||||
return ($this->getProtocol() == 'svn+ssh');
|
||||
}
|
||||
|
||||
protected function isHTTPProtocol() {
|
||||
return ($this->getProtocol() == 'http');
|
||||
}
|
||||
|
||||
protected function isHTTPSProtocol() {
|
||||
return ($this->getProtocol() == 'https');
|
||||
}
|
||||
|
||||
protected function isAnyHTTPProtocol() {
|
||||
return ($this->isHTTPProtocol() || $this->isHTTPSProtocol());
|
||||
}
|
||||
|
||||
protected function isAnySSHProtocol() {
|
||||
return ($this->isSSHProtocol() || $this->isSVNSSHProtocol());
|
||||
}
|
||||
|
||||
protected function getSSHWrapper() {
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
return $root.'/bin/ssh-connect';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionGitCommandEngine
|
||||
extends DiffusionCommandEngine {
|
||||
|
||||
protected function canBuildForRepository(
|
||||
PhabricatorRepository $repository) {
|
||||
return $repository->isGit();
|
||||
}
|
||||
|
||||
protected function newFormattedCommand($pattern, array $argv) {
|
||||
$pattern = "git {$pattern}";
|
||||
return array($pattern, $argv);
|
||||
}
|
||||
|
||||
protected function newCustomEnvironment() {
|
||||
$env = array();
|
||||
|
||||
// NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if
|
||||
// it can not read $HOME. For many users, $HOME points at /root (this
|
||||
// seems to be a default result of Apache setup). Instead, explicitly
|
||||
// point $HOME at a readable, empty directory so that Git looks for the
|
||||
// config file it's after, fails to locate it, and moves on. This is
|
||||
// really silly, but seems like the least damaging approach to
|
||||
// mitigating the issue.
|
||||
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
$env['HOME'] = $root.'/support/empty/';
|
||||
|
||||
if ($this->isAnySSHProtocol()) {
|
||||
$env['GIT_SSH'] = $this->getSSHWrapper();
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionMercurialCommandEngine
|
||||
extends DiffusionCommandEngine {
|
||||
|
||||
protected function canBuildForRepository(
|
||||
PhabricatorRepository $repository) {
|
||||
return $repository->isHg();
|
||||
}
|
||||
|
||||
protected function newFormattedCommand($pattern, array $argv) {
|
||||
$args = array();
|
||||
|
||||
if ($this->isAnySSHProtocol()) {
|
||||
$pattern = "hg --config ui.ssh=%s {$pattern}";
|
||||
$args[] = $this->getSSHWrapper();
|
||||
} else {
|
||||
$pattern = "hg {$pattern}";
|
||||
}
|
||||
|
||||
return array($pattern, array_merge($args, $argv));
|
||||
}
|
||||
|
||||
protected function newCustomEnvironment() {
|
||||
$env = array();
|
||||
|
||||
// NOTE: This overrides certain configuration, extensions, and settings
|
||||
// which make Mercurial commands do random unusual things.
|
||||
$env['HGPLAIN'] = 1;
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize output of an `hg` command invoked with the `--debug` flag to make
|
||||
* it usable.
|
||||
*
|
||||
* @param string Output from `hg --debug ...`
|
||||
* @return string Usable output.
|
||||
*/
|
||||
public static function filterMercurialDebugOutput($stdout) {
|
||||
// When hg commands are run with `--debug` and some config file isn't
|
||||
// trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011.
|
||||
//
|
||||
// http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
|
||||
//
|
||||
// After Jan 2015, it may also fail to write to a revision branch cache.
|
||||
|
||||
$ignore = array(
|
||||
'ignoring untrusted configuration option',
|
||||
"couldn't write revision branch cache:",
|
||||
);
|
||||
|
||||
foreach ($ignore as $key => $pattern) {
|
||||
$ignore[$key] = preg_quote($pattern, '/');
|
||||
}
|
||||
|
||||
$ignore = '('.implode('|', $ignore).')';
|
||||
|
||||
$lines = preg_split('/(?<=\n)/', $stdout);
|
||||
$regex = '/'.$ignore.'.*\n$/';
|
||||
|
||||
foreach ($lines as $key => $line) {
|
||||
$lines[$key] = preg_replace($regex, '', $line);
|
||||
}
|
||||
|
||||
return implode('', $lines);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionSubversionCommandEngine
|
||||
extends DiffusionCommandEngine {
|
||||
|
||||
protected function canBuildForRepository(
|
||||
PhabricatorRepository $repository) {
|
||||
return $repository->isSVN();
|
||||
}
|
||||
|
||||
protected function newFormattedCommand($pattern, array $argv) {
|
||||
$flags = array();
|
||||
$args = array();
|
||||
|
||||
$flags[] = '--non-interactive';
|
||||
|
||||
if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
|
||||
$flags[] = '--no-auth-cache';
|
||||
|
||||
if ($this->isAnyHTTPProtocol()) {
|
||||
$flags[] = '--trust-server-cert';
|
||||
}
|
||||
|
||||
$credential_phid = $this->getCredentialPHID();
|
||||
if ($credential_phid) {
|
||||
$key = PassphrasePasswordKey::loadFromPHID(
|
||||
$credential_phid,
|
||||
PhabricatorUser::getOmnipotentUser());
|
||||
|
||||
$flags[] = '--username %P';
|
||||
$args[] = $key->getUsernameEnvelope();
|
||||
|
||||
$flags[] = '--password %P';
|
||||
$args[] = $key->getPasswordEnvelope();
|
||||
}
|
||||
}
|
||||
|
||||
$flags = implode(' ', $flags);
|
||||
$pattern = "svn {$flags} {$pattern}";
|
||||
|
||||
return array($pattern, array_merge($args, $argv));
|
||||
}
|
||||
|
||||
protected function newCustomEnvironment() {
|
||||
$env = array();
|
||||
|
||||
if ($this->isAnySSHProtocol()) {
|
||||
$env['SVN_SSH'] = $this->getSSHWrapper();
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionCommandEngineTestCase extends PhabricatorTestCase {
|
||||
|
||||
public function testCommandEngine() {
|
||||
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
$type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
|
||||
$type_svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
|
||||
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
$ssh_wrapper = $root.'/bin/ssh-connect';
|
||||
$home = $root.'/support/empty/';
|
||||
|
||||
|
||||
// Plain commands.
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'git xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'HOME' => $home,
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_git,
|
||||
'argv' => 'xyz',
|
||||
));
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'hg xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'HGPLAIN' => '1',
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_hg,
|
||||
'argv' => 'xyz',
|
||||
));
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'svn --non-interactive xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_svn,
|
||||
'argv' => 'xyz',
|
||||
));
|
||||
|
||||
|
||||
// Commands with SSH.
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'git xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'HOME' => $home,
|
||||
'GIT_SSH' => $ssh_wrapper,
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_git,
|
||||
'argv' => 'xyz',
|
||||
'protocol' => 'ssh',
|
||||
));
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
(string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper),
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'HGPLAIN' => '1',
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_hg,
|
||||
'argv' => 'xyz',
|
||||
'protocol' => 'ssh',
|
||||
));
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'svn --non-interactive xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'SVN_SSH' => $ssh_wrapper,
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_svn,
|
||||
'argv' => 'xyz',
|
||||
'protocol' => 'ssh',
|
||||
));
|
||||
|
||||
|
||||
// Commands with HTTP.
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'git xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'HOME' => $home,
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_git,
|
||||
'argv' => 'xyz',
|
||||
'protocol' => 'https',
|
||||
));
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'hg xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
'HGPLAIN' => '1',
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_hg,
|
||||
'argv' => 'xyz',
|
||||
'protocol' => 'https',
|
||||
));
|
||||
|
||||
$this->assertCommandEngineFormat(
|
||||
'svn --non-interactive --no-auth-cache --trust-server-cert xyz',
|
||||
array(
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
),
|
||||
array(
|
||||
'vcs' => $type_svn,
|
||||
'argv' => 'xyz',
|
||||
'protocol' => 'https',
|
||||
));
|
||||
}
|
||||
|
||||
private function assertCommandEngineFormat(
|
||||
$command,
|
||||
array $env,
|
||||
array $inputs) {
|
||||
|
||||
$repository = id(new PhabricatorRepository())
|
||||
->setVersionControlSystem($inputs['vcs']);
|
||||
|
||||
$future = DiffusionCommandEngine::newCommandEngine($repository)
|
||||
->setArgv((array)$inputs['argv'])
|
||||
->setProtocol(idx($inputs, 'protocol'))
|
||||
->newFuture();
|
||||
|
||||
$command_string = $future->getCommand();
|
||||
|
||||
$actual_command = $command_string->getUnmaskedString();
|
||||
$this->assertEqual($command, $actual_command);
|
||||
|
||||
$actual_environment = $future->getEnv();
|
||||
|
||||
$compare_environment = array_select_keys(
|
||||
$actual_environment,
|
||||
array_keys($env));
|
||||
|
||||
$this->assertEqual($env, $compare_environment);
|
||||
}
|
||||
|
||||
}
|
|
@ -50,7 +50,9 @@ final class DiffusionLowLevelParentsQuery
|
|||
list($stdout) = $repository->execxLocalCommand(
|
||||
'log --debug --limit 1 --template={parents} --rev %s',
|
||||
$this->identifier);
|
||||
$stdout = PhabricatorRepository::filterMercurialDebugOutput($stdout);
|
||||
|
||||
$stdout = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
|
||||
$stdout);
|
||||
|
||||
$hashes = preg_split('/\s+/', trim($stdout));
|
||||
foreach ($hashes as $key => $value) {
|
||||
|
|
|
@ -21,22 +21,31 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
|
||||
if ($this->shouldProxy()) {
|
||||
$command = $this->getProxyCommand();
|
||||
$is_proxy = true;
|
||||
$did_synchronize = false;
|
||||
} else {
|
||||
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
|
||||
$is_proxy = false;
|
||||
|
||||
$repository->synchronizeWorkingCopyBeforeWrite();
|
||||
$did_synchronize = true;
|
||||
$viewer = $this->getUser();
|
||||
$repository->synchronizeWorkingCopyBeforeWrite($viewer);
|
||||
}
|
||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||
|
||||
$future = id(new ExecFuture('%C', $command))
|
||||
->setEnv($this->getEnvironment());
|
||||
$caught = null;
|
||||
try {
|
||||
$err = $this->executeRepositoryCommand($command);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$err = $this->newPassthruCommand()
|
||||
->setIOChannel($this->getIOChannel())
|
||||
->setCommandChannelFromExecFuture($future)
|
||||
->execute();
|
||||
// We've committed the write (or rejected it), so we can release the lock
|
||||
// without waiting for the client to receive the acknowledgement.
|
||||
if ($did_synchronize) {
|
||||
$repository->synchronizeWorkingCopyAfterWrite();
|
||||
}
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
}
|
||||
|
||||
if (!$err) {
|
||||
$repository->writeStatusMessage(
|
||||
|
@ -45,11 +54,20 @@ final class DiffusionGitReceivePackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
$this->waitForGitClient();
|
||||
}
|
||||
|
||||
if (!$is_proxy) {
|
||||
$repository->synchronizeWorkingCopyAfterWrite();
|
||||
}
|
||||
|
||||
return $err;
|
||||
}
|
||||
|
||||
private function executeRepositoryCommand($command) {
|
||||
$repository = $this->getRepository();
|
||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||
|
||||
$future = id(new ExecFuture('%C', $command))
|
||||
->setEnv($this->getEnvironment());
|
||||
|
||||
return $this->newPassthruCommand()
|
||||
->setIOChannel($this->getIOChannel())
|
||||
->setCommandChannelFromExecFuture($future)
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,12 +16,16 @@ final class DiffusionGitUploadPackSSHWorkflow extends DiffusionGitSSHWorkflow {
|
|||
protected function executeRepositoryOperations() {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$skip_sync = $this->shouldSkipReadSynchronization();
|
||||
|
||||
if ($this->shouldProxy()) {
|
||||
$command = $this->getProxyCommand();
|
||||
} else {
|
||||
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
|
||||
if (!$skip_sync) {
|
||||
$repository->synchronizeWorkingCopyBeforeRead();
|
||||
}
|
||||
}
|
||||
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
|
||||
|
||||
$future = id(new ExecFuture('%C', $command))
|
||||
|
|
|
@ -62,16 +62,13 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
protected function getProxyCommand() {
|
||||
$uri = new PhutilURI($this->proxyURI);
|
||||
|
||||
$username = PhabricatorEnv::getEnvConfig('cluster.instance');
|
||||
if (!strlen($username)) {
|
||||
$username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
|
||||
if (!strlen($username)) {
|
||||
$username = AlmanacKeys::getClusterSSHUser();
|
||||
if ($username === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to determine the username to connect with when trying '.
|
||||
'to proxy an SSH request within the Phabricator cluster.'));
|
||||
}
|
||||
}
|
||||
|
||||
$port = $uri->getPort();
|
||||
$host = $uri->getDomain();
|
||||
|
@ -204,6 +201,14 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
$repository = $this->getRepository();
|
||||
$viewer = $this->getUser();
|
||||
|
||||
if ($viewer->isOmnipotent()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'This request is authenticated as a cluster device, but is '.
|
||||
'performing a write. Writes must be performed with a real '.
|
||||
'user account.'));
|
||||
}
|
||||
|
||||
switch ($repository->getServeOverSSH()) {
|
||||
case PhabricatorRepository::SERVE_READONLY:
|
||||
if ($protocol_command !== null) {
|
||||
|
@ -239,4 +244,18 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
|
|||
return $this->hasWriteAccess;
|
||||
}
|
||||
|
||||
protected function shouldSkipReadSynchronization() {
|
||||
$viewer = $this->getUser();
|
||||
|
||||
// Currently, the only case where devices interact over SSH without
|
||||
// assuming user credentials is when synchronizing before a read. These
|
||||
// synchronizing reads do not themselves need to be synchronized.
|
||||
if ($viewer->isOmnipotent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
final class DiffusionPushLogListView extends AphrontView {
|
||||
|
||||
private $logs;
|
||||
private $handles;
|
||||
|
||||
public function setLogs(array $logs) {
|
||||
assert_instances_of($logs, 'PhabricatorRepositoryPushLog');
|
||||
|
@ -11,15 +10,20 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setHandles(array $handles) {
|
||||
$this->handles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$logs = $this->logs;
|
||||
$viewer = $this->getUser();
|
||||
$handles = $this->handles;
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$handle_phids = array();
|
||||
foreach ($logs as $log) {
|
||||
$handle_phids[] = $log->getPusherPHID();
|
||||
$device_phid = $log->getDevicePHID();
|
||||
if ($device_phid) {
|
||||
$handle_phids[] = $device_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($handle_phids);
|
||||
|
||||
// Figure out which repositories are editable. We only let you see remote
|
||||
// IPs if you have edit capability on a repository.
|
||||
|
@ -38,6 +42,7 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
}
|
||||
|
||||
$rows = array();
|
||||
$any_host = false;
|
||||
foreach ($logs as $log) {
|
||||
$repository = $log->getRepository();
|
||||
|
||||
|
@ -59,6 +64,14 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
$log->getRefOldShort());
|
||||
}
|
||||
|
||||
$device_phid = $log->getDevicePHID();
|
||||
if ($device_phid) {
|
||||
$device = $viewer->renderHandle($device_phid);
|
||||
$any_host = true;
|
||||
} else {
|
||||
$device = null;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
phutil_tag(
|
||||
'a',
|
||||
|
@ -72,9 +85,10 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
'href' => $repository->getURI(),
|
||||
),
|
||||
$repository->getDisplayName()),
|
||||
$handles[$log->getPusherPHID()]->renderLink(),
|
||||
$viewer->renderHandle($log->getPusherPHID()),
|
||||
$remote_address,
|
||||
$log->getPushEvent()->getRemoteProtocol(),
|
||||
$device,
|
||||
$log->getRefType(),
|
||||
$log->getRefName(),
|
||||
$old_ref_link,
|
||||
|
@ -100,6 +114,7 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
pht('Pusher'),
|
||||
pht('From'),
|
||||
pht('Via'),
|
||||
pht('Host'),
|
||||
pht('Type'),
|
||||
pht('Name'),
|
||||
pht('Old'),
|
||||
|
@ -116,10 +131,20 @@ final class DiffusionPushLogListView extends AphrontView {
|
|||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'wide',
|
||||
'n',
|
||||
'n',
|
||||
'right',
|
||||
))
|
||||
->setColumnVisibility(
|
||||
array(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
$any_host,
|
||||
));
|
||||
|
||||
return $table;
|
||||
|
|
|
@ -75,7 +75,7 @@ final class DivinerBookEditController extends DivinerController {
|
|||
id(new AphrontFormTokenizerControl())
|
||||
->setDatasource(new PhabricatorProjectDatasource())
|
||||
->setName('projectPHIDs')
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setValue($book->getProjectPHIDs()))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
|
|
|
@ -102,7 +102,7 @@ final class PhabricatorFeedSearchEngine
|
|||
);
|
||||
|
||||
if ($this->requireViewer()->isLoggedIn()) {
|
||||
$names['projects'] = pht('Projects');
|
||||
$names['projects'] = pht('Tags');
|
||||
}
|
||||
|
||||
return $names;
|
||||
|
|
|
@ -22,25 +22,21 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
|
|||
$req_domain = $request->getHost();
|
||||
$main_domain = id(new PhutilURI($base_uri))->getDomain();
|
||||
|
||||
|
||||
if (!strlen($alt) || $main_domain == $alt_domain) {
|
||||
// No alternate domain.
|
||||
$should_redirect = false;
|
||||
$use_viewer = $viewer;
|
||||
$is_alternate_domain = false;
|
||||
} else if ($req_domain != $alt_domain) {
|
||||
// Alternate domain, but this request is on the main domain.
|
||||
$should_redirect = true;
|
||||
$use_viewer = $viewer;
|
||||
$is_alternate_domain = false;
|
||||
} else {
|
||||
// Alternate domain, and on the alternate domain.
|
||||
$should_redirect = false;
|
||||
$use_viewer = PhabricatorUser::getOmnipotentUser();
|
||||
$is_alternate_domain = true;
|
||||
}
|
||||
|
||||
$response = $this->loadFile($use_viewer);
|
||||
$response = $this->loadFile();
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
@ -112,7 +108,21 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
|
|||
return $response;
|
||||
}
|
||||
|
||||
private function loadFile(PhabricatorUser $viewer) {
|
||||
private function loadFile() {
|
||||
// Access to files is provided by knowledge of a per-file secret key in
|
||||
// the URI. Knowledge of this secret is sufficient to retrieve the file.
|
||||
|
||||
// For some requests, we also have a valid viewer. However, for many
|
||||
// requests (like alternate domain requests or Git LFS requests) we will
|
||||
// not. Even if we do have a valid viewer, use the omnipotent viewer to
|
||||
// make this logic simpler and more consistent.
|
||||
|
||||
// Beyond making the policy check itself more consistent, this also makes
|
||||
// sure we're consitent about returning HTTP 404 on bad requests instead
|
||||
// of serving HTTP 200 with a login page, which can mislead some clients.
|
||||
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($this->phid))
|
||||
|
|
|
@ -201,7 +201,7 @@ final class FundInitiativeEditController
|
|||
->setValue($v_risk))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()))
|
||||
|
|
|
@ -58,7 +58,7 @@ final class ManiphestExcelDefaultFormat extends ManiphestExcelFormat {
|
|||
pht('Date Created'),
|
||||
pht('Date Updated'),
|
||||
pht('Title'),
|
||||
pht('Projects'),
|
||||
pht('Tags'),
|
||||
pht('URI'),
|
||||
pht('Description'),
|
||||
);
|
||||
|
|
|
@ -37,8 +37,10 @@ final class PhabricatorApplicationApplicationPHIDType
|
|||
foreach ($handles as $phid => $handle) {
|
||||
$application = $objects[$phid];
|
||||
|
||||
$handle->setName($application->getName());
|
||||
$handle->setURI($application->getApplicationURI());
|
||||
$handle
|
||||
->setName($application->getName())
|
||||
->setURI($application->getApplicationURI())
|
||||
->setIcon($application->getIcon());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,17 @@ final class OwnersQueryConduitAPIMethod extends OwnersConduitAPIMethod {
|
|||
return 'owners.query';
|
||||
}
|
||||
|
||||
public function getMethodStatus() {
|
||||
return self::METHOD_STATUS_DEPRECATED;
|
||||
}
|
||||
|
||||
public function getMethodStatusDescription() {
|
||||
return pht('Obsolete; use "owners.search" instead.');
|
||||
}
|
||||
|
||||
|
||||
public function getMethodDescription() {
|
||||
return pht(
|
||||
'Query for packages by one of the following: repository/path, '.
|
||||
'packages with a given user or project owner, or packages affiliated '.
|
||||
'with a user (owned by either the user or a project they are a member '.
|
||||
'of.) You should only provide at most one search query.');
|
||||
return pht('Query for Owners packages. Obsoleted by "owners.search".');
|
||||
}
|
||||
|
||||
protected function defineParamTypes() {
|
||||
|
|
|
@ -316,7 +316,7 @@ final class PholioMockEditController extends PholioController {
|
|||
->setUser($viewer))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()))
|
||||
|
|
|
@ -59,7 +59,7 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication {
|
|||
'new/' => 'PhrictionNewController',
|
||||
'move/(?P<id>[1-9]\d*)/' => 'PhrictionMoveController',
|
||||
|
||||
'preview/' => 'PhabricatorMarkupPreviewController',
|
||||
'preview/(?P<slug>.+/)' => 'PhrictionMarkupPreviewController',
|
||||
'diff/(?P<id>[1-9]\d*)/' => 'PhrictionDiffController',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -272,7 +272,7 @@ final class PhrictionEditController
|
|||
|
||||
$preview = id(new PHUIRemarkupPreviewPanel())
|
||||
->setHeader($content->getTitle())
|
||||
->setPreviewURI('/phriction/preview/')
|
||||
->setPreviewURI('/phriction/preview/'.$document->getSlug())
|
||||
->setControlID('document-textarea')
|
||||
->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT);
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
final class PhrictionMarkupPreviewController
|
||||
extends PhabricatorController {
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$text = $request->getStr('text');
|
||||
$slug = $request->getURIData('slug');
|
||||
|
||||
$output = PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())
|
||||
->setPreserveLinebreaks(true)
|
||||
->setDisableCache(true)
|
||||
->setContent($text),
|
||||
'default',
|
||||
$viewer,
|
||||
array(
|
||||
'phriction.isPreview' => true,
|
||||
'phriction.slug' => $slug,
|
||||
));
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent($output);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
final class PhrictionRemarkupRule extends PhutilRemarkupRule {
|
||||
|
||||
const KEY_RULE_PHRICTION_LINK = 'phriction.link';
|
||||
|
||||
public function getPriority() {
|
||||
return 175.0;
|
||||
}
|
||||
|
@ -15,11 +17,128 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule {
|
|||
|
||||
public function markupDocumentLink(array $matches) {
|
||||
$link = trim($matches[1]);
|
||||
$name = trim(idx($matches, 2, $link));
|
||||
|
||||
// Handle relative links.
|
||||
if ((substr($link, 0, 2) === './') || (substr($link, 0, 3) === '../')) {
|
||||
$base = null;
|
||||
$context = $this->getEngine()->getConfig('contextObject');
|
||||
if ($context !== null && $context instanceof PhrictionContent) {
|
||||
// Handle content when it's being rendered in document view.
|
||||
$base = $context->getSlug();
|
||||
}
|
||||
if ($context !== null && is_array($context) &&
|
||||
idx($context, 'phriction.isPreview')) {
|
||||
// Handle content when it's a preview for the Phriction editor.
|
||||
$base = idx($context, 'phriction.slug');
|
||||
}
|
||||
if ($base !== null) {
|
||||
$base_parts = explode('/', rtrim($base, '/'));
|
||||
$rel_parts = explode('/', rtrim($link, '/'));
|
||||
foreach ($rel_parts as $part) {
|
||||
if ($part === '.') {
|
||||
// Consume standalone dots in a relative path, and do
|
||||
// nothing with them.
|
||||
} else if ($part === '..') {
|
||||
if (count($base_parts) > 0) {
|
||||
array_pop($base_parts);
|
||||
}
|
||||
} else {
|
||||
array_push($base_parts, $part);
|
||||
}
|
||||
}
|
||||
$link = implode('/', $base_parts).'/';
|
||||
}
|
||||
}
|
||||
|
||||
$name = trim(idx($matches, 2, ''));
|
||||
if (empty($matches[2])) {
|
||||
$name = explode('/', trim($name, '/'));
|
||||
$name = null;
|
||||
}
|
||||
|
||||
// Link is now used for slug detection, so append a slash if one
|
||||
// is needed.
|
||||
$link = rtrim($link, '/').'/';
|
||||
|
||||
$engine = $this->getEngine();
|
||||
$token = $engine->storeText('x');
|
||||
$metadata = $engine->getTextMetadata(
|
||||
self::KEY_RULE_PHRICTION_LINK,
|
||||
array());
|
||||
$metadata[] = array(
|
||||
'token' => $token,
|
||||
'link' => $link,
|
||||
'explicitName' => $name,
|
||||
);
|
||||
$engine->setTextMetadata(self::KEY_RULE_PHRICTION_LINK, $metadata);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function didMarkupText() {
|
||||
$engine = $this->getEngine();
|
||||
$metadata = $engine->getTextMetadata(
|
||||
self::KEY_RULE_PHRICTION_LINK,
|
||||
array());
|
||||
|
||||
if (!$metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
$slugs = ipull($metadata, 'link');
|
||||
foreach ($slugs as $key => $slug) {
|
||||
$slugs[$key] = PhabricatorSlug::normalize($slug);
|
||||
}
|
||||
|
||||
// We have to make two queries here to distinguish between
|
||||
// documents the user can't see, and documents that don't
|
||||
// exist.
|
||||
$visible_documents = id(new PhrictionDocumentQuery())
|
||||
->setViewer($engine->getConfig('viewer'))
|
||||
->withSlugs($slugs)
|
||||
->needContent(true)
|
||||
->execute();
|
||||
$existant_documents = id(new PhrictionDocumentQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withSlugs($slugs)
|
||||
->execute();
|
||||
|
||||
$visible_documents = mpull($visible_documents, null, 'getSlug');
|
||||
$existant_documents = mpull($existant_documents, null, 'getSlug');
|
||||
|
||||
foreach ($metadata as $spec) {
|
||||
$link = $spec['link'];
|
||||
$slug = PhabricatorSlug::normalize($link);
|
||||
$name = $spec['explicitName'];
|
||||
$class = 'phriction-link';
|
||||
|
||||
// If the name is something meaningful to humans, we'll render this
|
||||
// in text as: "Title" <link>. Otherwise, we'll just render: <link>.
|
||||
$is_interesting_name = (bool)strlen($name);
|
||||
|
||||
if (idx($existant_documents, $slug) === null) {
|
||||
// The target document doesn't exist.
|
||||
if ($name === null) {
|
||||
$name = explode('/', trim($link, '/'));
|
||||
$name = end($name);
|
||||
}
|
||||
$class = 'phriction-link-missing';
|
||||
} else if (idx($visible_documents, $slug) === null) {
|
||||
// The document exists, but the user can't see it.
|
||||
if ($name === null) {
|
||||
$name = explode('/', trim($link, '/'));
|
||||
$name = end($name);
|
||||
}
|
||||
$class = 'phriction-link-lock';
|
||||
} else {
|
||||
if ($name === null) {
|
||||
// Use the title of the document if no name is set.
|
||||
$name = $visible_documents[$slug]
|
||||
->getContent()
|
||||
->getTitle();
|
||||
|
||||
$is_interesting_name = true;
|
||||
}
|
||||
}
|
||||
|
||||
$uri = new PhutilURI($link);
|
||||
$slug = $uri->getPath();
|
||||
|
@ -34,18 +153,36 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule {
|
|||
if ($this->getEngine()->getState('toc')) {
|
||||
$text = $name;
|
||||
} else if ($text_mode || $mail_mode) {
|
||||
return PhabricatorEnv::getProductionURI($href);
|
||||
$href = PhabricatorEnv::getProductionURI($href);
|
||||
if ($is_interesting_name) {
|
||||
$text = pht('"%s" <%s>', $name, $href);
|
||||
} else {
|
||||
$text = pht('<%s>', $href);
|
||||
}
|
||||
} else {
|
||||
if ($class === 'phriction-link-lock') {
|
||||
$name = array(
|
||||
$this->newTag(
|
||||
'i',
|
||||
array(
|
||||
'class' => 'phui-icon-view phui-font-fa fa-lock',
|
||||
),
|
||||
''),
|
||||
' ',
|
||||
$name,
|
||||
);
|
||||
}
|
||||
$text = $this->newTag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $href,
|
||||
'class' => 'phriction-link',
|
||||
'class' => $class,
|
||||
),
|
||||
$name);
|
||||
}
|
||||
|
||||
return $this->getEngine()->storeText($text);
|
||||
$this->getEngine()->overwriteStoredText($spec['token'], $text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ final class PhrictionContent extends PhrictionDAO
|
|||
return PhabricatorMarkupEngine::renderOneObject(
|
||||
$this,
|
||||
self::MARKUP_FIELD_BODY,
|
||||
$viewer);
|
||||
$viewer,
|
||||
$this);
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
|
|
|
@ -165,7 +165,7 @@ final class PhabricatorPhurlURLEditController
|
|||
->setError($error_alias);
|
||||
|
||||
$projects = id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($projects)
|
||||
->setUser($viewer)
|
||||
|
|
|
@ -156,7 +156,7 @@ final class PonderQuestionEditController extends PonderController {
|
|||
|
||||
$form->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()));
|
||||
|
|
|
@ -7,10 +7,10 @@ final class PhabricatorRepositoryType extends Phobject {
|
|||
const REPOSITORY_TYPE_MERCURIAL = 'hg';
|
||||
|
||||
public static function getAllRepositoryTypes() {
|
||||
static $map = array(
|
||||
self::REPOSITORY_TYPE_GIT => 'Git',
|
||||
self::REPOSITORY_TYPE_SVN => 'Subversion',
|
||||
self::REPOSITORY_TYPE_MERCURIAL => 'Mercurial',
|
||||
$map = array(
|
||||
self::REPOSITORY_TYPE_GIT => pht('Git'),
|
||||
self::REPOSITORY_TYPE_MERCURIAL => pht('Mercurial'),
|
||||
self::REPOSITORY_TYPE_SVN => pht('Subversion'),
|
||||
);
|
||||
return $map;
|
||||
}
|
||||
|
|
|
@ -248,25 +248,7 @@ final class PhabricatorRepositoryEditor
|
|||
$object->setCallsign($xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
|
||||
// Make sure the encoding is valid by converting to UTF-8. This tests
|
||||
// that the user has mbstring installed, and also that they didn't type
|
||||
// a garbage encoding name. Note that we're converting from UTF-8 to
|
||||
// the target encoding, because mbstring is fine with converting from
|
||||
// a nonsense encoding.
|
||||
$encoding = $xaction->getNewValue();
|
||||
if (strlen($encoding)) {
|
||||
try {
|
||||
phutil_utf8_convert('.', $encoding, 'UTF-8');
|
||||
} catch (Exception $ex) {
|
||||
throw new PhutilProxyException(
|
||||
pht(
|
||||
"Error setting repository encoding '%s': %s'",
|
||||
$encoding,
|
||||
$ex->getMessage()),
|
||||
$ex);
|
||||
}
|
||||
}
|
||||
$object->setDetail('encoding', $encoding);
|
||||
$object->setDetail('encoding', $xaction->getNewValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -461,6 +443,117 @@ final class PhabricatorRepositoryEditor
|
|||
}
|
||||
break;
|
||||
|
||||
case PhabricatorRepositoryTransaction::TYPE_VCS:
|
||||
$vcs_map = PhabricatorRepositoryType::getAllRepositoryTypes();
|
||||
$current_vcs = $object->getVersionControlSystem();
|
||||
|
||||
if (!$this->getIsNewObject()) {
|
||||
foreach ($xactions as $xaction) {
|
||||
if ($xaction->getNewValue() == $current_vcs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Immutable'),
|
||||
pht(
|
||||
'You can not change the version control system an existing '.
|
||||
'repository uses. It can only be set when a repository is '.
|
||||
'first created.'),
|
||||
$xaction);
|
||||
}
|
||||
} else {
|
||||
$value = $object->getVersionControlSystem();
|
||||
foreach ($xactions as $xaction) {
|
||||
$value = $xaction->getNewValue();
|
||||
|
||||
if (empty($vcs_map[$value])) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Specified version control system must be a VCS '.
|
||||
'recognized by Phabricator: %s.',
|
||||
implode(', ', array_keys($vcs_map))),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
||||
if (!strlen($value)) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht(
|
||||
'When creating a repository, you must specify a valid '.
|
||||
'underlying version control system: %s.',
|
||||
implode(', ', array_keys($vcs_map))),
|
||||
nonempty(last($xactions), null));
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorRepositoryTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('Repository name is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
|
||||
$status_map = PhabricatorRepository::getStatusMap();
|
||||
foreach ($xactions as $xaction) {
|
||||
$status = $xaction->getNewValue();
|
||||
if (empty($status_map[$status])) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Repository status "%s" is not valid.',
|
||||
$status),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
|
||||
foreach ($xactions as $xaction) {
|
||||
// Make sure the encoding is valid by converting to UTF-8. This tests
|
||||
// that the user has mbstring installed, and also that they didn't
|
||||
// type a garbage encoding name. Note that we're converting from
|
||||
// UTF-8 to the target encoding, because mbstring is fine with
|
||||
// converting from a nonsense encoding.
|
||||
$encoding = $xaction->getNewValue();
|
||||
if (!strlen($encoding)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
phutil_utf8_convert('.', $encoding, 'UTF-8');
|
||||
} catch (Exception $ex) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Repository encoding "%s" is not valid: %s',
|
||||
$encoding,
|
||||
$ex->getMessage()),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorRepositoryTransaction::TYPE_SLUG:
|
||||
foreach ($xactions as $xaction) {
|
||||
$old = $xaction->getOldValue();
|
||||
|
@ -590,6 +683,10 @@ final class PhabricatorRepositoryEditor
|
|||
$object->save();
|
||||
}
|
||||
|
||||
if ($this->getIsNewObject()) {
|
||||
$object->synchronizeWorkingCopyAfterCreation();
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,8 @@ final class PhabricatorRepositoryPullEngine
|
|||
}
|
||||
|
||||
if ($repository->isHosted()) {
|
||||
$repository->synchronizeWorkingCopyBeforeRead();
|
||||
|
||||
if ($is_git) {
|
||||
$this->installGitHook();
|
||||
} else if ($is_svn) {
|
||||
|
|
|
@ -43,6 +43,7 @@ final class PhabricatorRepositoryManagementLookupUsersWorkflow
|
|||
)),
|
||||
'diffusion.querycommits',
|
||||
array(
|
||||
'repositoryPHID' => $repo->getPHID(),
|
||||
'phids' => array($commit->getPHID()),
|
||||
'bypassCache' => true,
|
||||
));
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositoryManagementThawWorkflow
|
||||
extends PhabricatorRepositoryManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->setName('thaw')
|
||||
->setExamples('**thaw** [options] __repository__ ...')
|
||||
->setSynopsis(
|
||||
pht(
|
||||
'Resolve issues with frozen cluster repositories. Very advanced '.
|
||||
'and dangerous.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'demote',
|
||||
'param' => 'device',
|
||||
'help' => pht(
|
||||
'Demote a device, discarding local changes. Clears stuck '.
|
||||
'write locks and recovers from lost leaders.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'promote',
|
||||
'param' => 'device',
|
||||
'help' => pht(
|
||||
'Promote a device, discarding changes on other devices. '.
|
||||
'Resolves ambiguous leadership and recovers from demotion '.
|
||||
'mistakes.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'force',
|
||||
'help' => pht('Run operations without asking for confirmation.'),
|
||||
),
|
||||
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 thaw.'));
|
||||
}
|
||||
|
||||
$promote = $args->getArg('promote');
|
||||
$demote = $args->getArg('demote');
|
||||
|
||||
if (!$promote && !$demote) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('You must choose a device to --promote or --demote.'));
|
||||
}
|
||||
|
||||
if ($promote && $demote) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('Specify either --promote or --demote, but not both.'));
|
||||
}
|
||||
|
||||
$device_name = nonempty($promote, $demote);
|
||||
|
||||
$device = id(new AlmanacDeviceQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($device_name))
|
||||
->executeOne();
|
||||
if (!$device) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('No device "%s" exists.', $device_name));
|
||||
}
|
||||
|
||||
if ($promote) {
|
||||
$risk_message = pht(
|
||||
'Promoting a device can cause the loss of any repository data which '.
|
||||
'only exists on other devices. The version of the repository on the '.
|
||||
'promoted device will become authoritative.');
|
||||
} else {
|
||||
$risk_message = pht(
|
||||
'Demoting a device can cause the loss of any repository data which '.
|
||||
'only exists on the demoted device. The version of the repository '.
|
||||
'on some other device will become authoritative.');
|
||||
}
|
||||
|
||||
echo tsprintf(
|
||||
"**<bg:red> %s </bg>** %s\n",
|
||||
pht('DATA AT RISK'),
|
||||
$risk_message);
|
||||
|
||||
$is_force = $args->getArg('force');
|
||||
$prompt = pht('Accept the possibilty of permanent data loss?');
|
||||
if (!$is_force && !phutil_console_confirm($prompt)) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('User aborted the workflow.'));
|
||||
}
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$repository_phid = $repository->getPHID();
|
||||
|
||||
$write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock(
|
||||
$repository_phid);
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Waiting to acquire write lock for "%s"...',
|
||||
$repository->getDisplayName()));
|
||||
|
||||
$write_lock->lock(phutil_units('5 minutes in seconds'));
|
||||
try {
|
||||
|
||||
$service = $repository->loadAlmanacService();
|
||||
if (!$service) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" is not a cluster repository: it is not '.
|
||||
'bound to an Almanac service.',
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
$bindings = $service->getActiveBindings();
|
||||
$bindings = mpull($bindings, null, 'getDevicePHID');
|
||||
if (empty($bindings[$device->getPHID()])) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Repository "%s" has no active binding to device "%s". Only '.
|
||||
'actively bound devices can be promoted or demoted.',
|
||||
$repository->getDisplayName(),
|
||||
$device->getName()));
|
||||
}
|
||||
|
||||
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
||||
$repository->getPHID());
|
||||
|
||||
$versions = mpull($versions, null, 'getDevicePHID');
|
||||
$versions = array_select_keys($versions, array_keys($bindings));
|
||||
|
||||
if ($versions && $promote) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Unable to promote "%s" for repository "%s": the leaders for '.
|
||||
'this cluster are not ambiguous.',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
if ($promote) {
|
||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||
$repository->getPHID(),
|
||||
$device->getPHID(),
|
||||
0);
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Promoted "%s" to become a leader for "%s".',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
|
||||
if ($demote) {
|
||||
PhabricatorRepositoryWorkingCopyVersion::demoteDevice(
|
||||
$repository->getPHID(),
|
||||
$device->getPHID());
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Demoted "%s" from leadership of repository "%s".',
|
||||
$device->getName(),
|
||||
$repository->getDisplayName()));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$write_lock->unlock();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$write_lock->unlock();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -92,20 +92,13 @@ final class PhabricatorRepositoryPushLogSearchEngine
|
|||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function getRequiredHandlePHIDsForResultList(
|
||||
array $logs,
|
||||
PhabricatorSavedQuery $query) {
|
||||
return mpull($logs, 'getPusherPHID');
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $logs,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
|
||||
$table = id(new DiffusionPushLogListView())
|
||||
->setUser($this->requireViewer())
|
||||
->setHandles($handles)
|
||||
->setViewer($this->requireViewer())
|
||||
->setLogs($logs);
|
||||
|
||||
return id(new PhabricatorApplicationSearchResultView())
|
||||
|
|
|
@ -13,7 +13,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
PhabricatorMarkupInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorProjectInterface,
|
||||
PhabricatorSpacesInterface {
|
||||
PhabricatorSpacesInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
|
||||
/**
|
||||
* Shortest hash we'll recognize in raw "a829f32" form.
|
||||
|
@ -45,6 +46,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose';
|
||||
const BECAUSE_AUTOCLOSE_FORCED = 'auto/forced';
|
||||
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
|
||||
protected $name;
|
||||
protected $callsign;
|
||||
protected $repositorySlug;
|
||||
|
@ -62,10 +66,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
private $commitCount = self::ATTACHABLE;
|
||||
private $mostRecentCommit = self::ATTACHABLE;
|
||||
private $projectPHIDs = self::ATTACHABLE;
|
||||
private $uris = self::ATTACHABLE;
|
||||
|
||||
private $clusterWriteLock;
|
||||
private $clusterWriteVersion;
|
||||
|
||||
|
||||
public static function initializeNewRepository(PhabricatorUser $actor) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($actor)
|
||||
|
@ -129,6 +135,31 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public static function getStatusMap() {
|
||||
return array(
|
||||
self::STATUS_ACTIVE => array(
|
||||
'name' => pht('Active'),
|
||||
'isTracked' => 1,
|
||||
),
|
||||
self::STATUS_INACTIVE => array(
|
||||
'name' => pht('Inactive'),
|
||||
'isTracked' => 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static function getStatusNameMap() {
|
||||
return ipull(self::getStatusMap(), 'name');
|
||||
}
|
||||
|
||||
public function getStatus() {
|
||||
if ($this->isTracked()) {
|
||||
return self::STATUS_ACTIVE;
|
||||
} else {
|
||||
return self::STATUS_INACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
public function toDictionary() {
|
||||
return array(
|
||||
'id' => $this->getID(),
|
||||
|
@ -143,7 +174,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
'isActive' => $this->isTracked(),
|
||||
'isHosted' => $this->isHosted(),
|
||||
'isImporting' => $this->isImporting(),
|
||||
'encoding' => $this->getDetail('encoding'),
|
||||
'encoding' => $this->getDefaultTextEncoding(),
|
||||
'staging' => array(
|
||||
'supported' => $this->supportsStaging(),
|
||||
'prefix' => 'phabricator',
|
||||
|
@ -152,6 +183,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
);
|
||||
}
|
||||
|
||||
public function getDefaultTextEncoding() {
|
||||
return $this->getDetail('encoding', 'UTF-8');
|
||||
}
|
||||
|
||||
public function getMonogram() {
|
||||
$callsign = $this->getCallsign();
|
||||
if (strlen($callsign)) {
|
||||
|
@ -452,19 +487,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
private function newRemoteCommandFuture(array $argv) {
|
||||
$argv = $this->formatRemoteCommand($argv);
|
||||
$future = newv('ExecFuture', $argv);
|
||||
$future->setEnv($this->getRemoteCommandEnvironment());
|
||||
return $future;
|
||||
return $this->newRemoteCommandEngine($argv)
|
||||
->newFuture();
|
||||
}
|
||||
|
||||
private function newRemoteCommandPassthru(array $argv) {
|
||||
$argv = $this->formatRemoteCommand($argv);
|
||||
$passthru = newv('PhutilExecPassthru', $argv);
|
||||
$passthru->setEnv($this->getRemoteCommandEnvironment());
|
||||
return $passthru;
|
||||
return $this->newRemoteCommandEngine($argv)
|
||||
->setPassthru(true)
|
||||
->newFuture();
|
||||
}
|
||||
|
||||
private function newRemoteCommandEngine(array $argv) {
|
||||
return DiffusionCommandEngine::newCommandEngine($this)
|
||||
->setArgv($argv)
|
||||
->setCredentialPHID($this->getCredentialPHID())
|
||||
->setProtocol($this->getRemoteProtocol());
|
||||
}
|
||||
|
||||
/* -( Local Command Execution )-------------------------------------------- */
|
||||
|
||||
|
@ -492,9 +530,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
private function newLocalCommandFuture(array $argv) {
|
||||
$this->assertLocalExists();
|
||||
|
||||
$argv = $this->formatLocalCommand($argv);
|
||||
$future = newv('ExecFuture', $argv);
|
||||
$future->setEnv($this->getLocalCommandEnvironment());
|
||||
$future = DiffusionCommandEngine::newCommandEngine($this)
|
||||
->setArgv($argv)
|
||||
->newFuture();
|
||||
|
||||
if ($this->usesLocalWorkingCopy()) {
|
||||
$future->setCWD($this->getLocalPath());
|
||||
|
@ -506,9 +544,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
private function newLocalCommandPassthru(array $argv) {
|
||||
$this->assertLocalExists();
|
||||
|
||||
$argv = $this->formatLocalCommand($argv);
|
||||
$future = newv('PhutilExecPassthru', $argv);
|
||||
$future->setEnv($this->getLocalCommandEnvironment());
|
||||
$future = DiffusionCommandEngine::newCommandEngine($this)
|
||||
->setArgv($argv)
|
||||
->setPassthru(true)
|
||||
->newFuture();
|
||||
|
||||
if ($this->usesLocalWorkingCopy()) {
|
||||
$future->setCWD($this->getLocalPath());
|
||||
|
@ -517,199 +556,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $future;
|
||||
}
|
||||
|
||||
|
||||
/* -( Command Infrastructure )--------------------------------------------- */
|
||||
|
||||
|
||||
private function getSSHWrapper() {
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
return $root.'/bin/ssh-connect';
|
||||
}
|
||||
|
||||
private function getCommonCommandEnvironment() {
|
||||
$env = array(
|
||||
// NOTE: Force the language to "en_US.UTF-8", which overrides locale
|
||||
// settings. This makes stuff print in English instead of, e.g., French,
|
||||
// so we can parse the output of some commands, error messages, etc.
|
||||
'LANG' => 'en_US.UTF-8',
|
||||
|
||||
// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
|
||||
'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(),
|
||||
);
|
||||
|
||||
switch ($this->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
// NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if
|
||||
// it can not read $HOME. For many users, $HOME points at /root (this
|
||||
// seems to be a default result of Apache setup). Instead, explicitly
|
||||
// point $HOME at a readable, empty directory so that Git looks for the
|
||||
// config file it's after, fails to locate it, and moves on. This is
|
||||
// really silly, but seems like the least damaging approach to
|
||||
// mitigating the issue.
|
||||
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
$env['HOME'] = $root.'/support/empty/';
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
// NOTE: This overrides certain configuration, extensions, and settings
|
||||
// which make Mercurial commands do random unusual things.
|
||||
$env['HGPLAIN'] = 1;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unrecognized version control system.'));
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
private function getLocalCommandEnvironment() {
|
||||
return $this->getCommonCommandEnvironment();
|
||||
}
|
||||
|
||||
private function getRemoteCommandEnvironment() {
|
||||
$env = $this->getCommonCommandEnvironment();
|
||||
|
||||
if ($this->shouldUseSSH()) {
|
||||
// NOTE: This is read by `bin/ssh-connect`, and tells it which credentials
|
||||
// to use.
|
||||
$env['PHABRICATOR_CREDENTIAL'] = $this->getCredentialPHID();
|
||||
switch ($this->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
// Force SVN to use `bin/ssh-connect`.
|
||||
$env['SVN_SSH'] = $this->getSSHWrapper();
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
// Force Git to use `bin/ssh-connect`.
|
||||
$env['GIT_SSH'] = $this->getSSHWrapper();
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
// We force Mercurial through `bin/ssh-connect` too, but it uses a
|
||||
// command-line flag instead of an environmental variable.
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unrecognized version control system.'));
|
||||
}
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
private function formatRemoteCommand(array $args) {
|
||||
$pattern = $args[0];
|
||||
$args = array_slice($args, 1);
|
||||
|
||||
switch ($this->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
if ($this->shouldUseHTTP() || $this->shouldUseSVNProtocol()) {
|
||||
$flags = array();
|
||||
$flag_args = array();
|
||||
$flags[] = '--non-interactive';
|
||||
$flags[] = '--no-auth-cache';
|
||||
if ($this->shouldUseHTTP()) {
|
||||
$flags[] = '--trust-server-cert';
|
||||
}
|
||||
|
||||
$credential_phid = $this->getCredentialPHID();
|
||||
if ($credential_phid) {
|
||||
$key = PassphrasePasswordKey::loadFromPHID(
|
||||
$credential_phid,
|
||||
PhabricatorUser::getOmnipotentUser());
|
||||
$flags[] = '--username %P';
|
||||
$flags[] = '--password %P';
|
||||
$flag_args[] = $key->getUsernameEnvelope();
|
||||
$flag_args[] = $key->getPasswordEnvelope();
|
||||
}
|
||||
|
||||
$flags = implode(' ', $flags);
|
||||
$pattern = "svn {$flags} {$pattern}";
|
||||
$args = array_mergev(array($flag_args, $args));
|
||||
} else {
|
||||
$pattern = "svn --non-interactive {$pattern}";
|
||||
}
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$pattern = "git {$pattern}";
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
if ($this->shouldUseSSH()) {
|
||||
$pattern = "hg --config ui.ssh=%s {$pattern}";
|
||||
array_unshift(
|
||||
$args,
|
||||
$this->getSSHWrapper());
|
||||
} else {
|
||||
$pattern = "hg {$pattern}";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unrecognized version control system.'));
|
||||
}
|
||||
|
||||
array_unshift($args, $pattern);
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
private function formatLocalCommand(array $args) {
|
||||
$pattern = $args[0];
|
||||
$args = array_slice($args, 1);
|
||||
|
||||
switch ($this->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
$pattern = "svn --non-interactive {$pattern}";
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$pattern = "git {$pattern}";
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$pattern = "hg {$pattern}";
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unrecognized version control system.'));
|
||||
}
|
||||
|
||||
array_unshift($args, $pattern);
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize output of an `hg` command invoked with the `--debug` flag to make
|
||||
* it usable.
|
||||
*
|
||||
* @param string Output from `hg --debug ...`
|
||||
* @return string Usable output.
|
||||
*/
|
||||
public static function filterMercurialDebugOutput($stdout) {
|
||||
// When hg commands are run with `--debug` and some config file isn't
|
||||
// trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011.
|
||||
//
|
||||
// http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
|
||||
//
|
||||
// After Jan 2015, it may also fail to write to a revision branch cache.
|
||||
|
||||
$ignore = array(
|
||||
'ignoring untrusted configuration option',
|
||||
"couldn't write revision branch cache:",
|
||||
);
|
||||
|
||||
foreach ($ignore as $key => $pattern) {
|
||||
$ignore[$key] = preg_quote($pattern, '/');
|
||||
}
|
||||
|
||||
$ignore = '('.implode('|', $ignore).')';
|
||||
|
||||
$lines = preg_split('/(?<=\n)/', $stdout);
|
||||
$regex = '/'.$ignore.'.*\n$/';
|
||||
|
||||
foreach ($lines as $key => $line) {
|
||||
$lines[$key] = preg_replace($regex, '', $line);
|
||||
}
|
||||
|
||||
return implode('', $lines);
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$callsign = $this->getCallsign();
|
||||
if (strlen($callsign)) {
|
||||
|
@ -991,7 +837,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
public function isTracked() {
|
||||
return $this->getDetail('tracking-enabled', false);
|
||||
$status = $this->getDetail('tracking-enabled');
|
||||
$map = self::getStatusMap();
|
||||
$spec = idx($map, $status);
|
||||
|
||||
if (!$spec) {
|
||||
if ($status) {
|
||||
$status = self::STATUS_ACTIVE;
|
||||
} else {
|
||||
$status = self::STATUS_INACTIVE;
|
||||
}
|
||||
$spec = idx($map, $status);
|
||||
}
|
||||
|
||||
return (bool)idx($spec, 'isTracked', false);
|
||||
}
|
||||
|
||||
public function getDefaultBranch() {
|
||||
|
@ -1489,8 +1348,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$uri->setPath($uri->getPath().$this->getCloneName().'/');
|
||||
}
|
||||
|
||||
$ssh_user = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
|
||||
if ($ssh_user) {
|
||||
$ssh_user = AlmanacKeys::getClusterSSHUser();
|
||||
if (strlen($ssh_user)) {
|
||||
$uri->setUser($ssh_user);
|
||||
}
|
||||
|
||||
|
@ -2068,34 +1927,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$never_proxy,
|
||||
array $protocols) {
|
||||
|
||||
$service_phid = $this->getAlmanacServicePHID();
|
||||
if (!$service_phid) {
|
||||
// No service, so this is a local repository.
|
||||
$service = $this->loadAlmanacService();
|
||||
if (!$service) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$service = id(new AlmanacServiceQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(array($service_phid))
|
||||
->needBindings(true)
|
||||
->needProperties(true)
|
||||
->executeOne();
|
||||
if (!$service) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository is invalid or could not '.
|
||||
'be loaded.'));
|
||||
}
|
||||
|
||||
$service_type = $service->getServiceImplementation();
|
||||
if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository does not have the correct '.
|
||||
'service type.'));
|
||||
}
|
||||
|
||||
$bindings = $service->getBindings();
|
||||
$bindings = $service->getActiveBindings();
|
||||
if (!$bindings) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -2131,16 +1968,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
}
|
||||
|
||||
$protocol = $binding->getAlmanacPropertyValue('protocol');
|
||||
if ($protocol === null) {
|
||||
$protocol = 'https';
|
||||
}
|
||||
$uri = $this->getClusterRepositoryURIFromBinding($binding);
|
||||
|
||||
$protocol = $uri->getProtocol();
|
||||
if (empty($protocol_map[$protocol])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$uris[] = $protocol.'://'.$iface->renderDisplayAddress().'/';
|
||||
$uris[] = $uri;
|
||||
}
|
||||
|
||||
if (!$uris) {
|
||||
|
@ -2265,13 +2100,119 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $client;
|
||||
}
|
||||
|
||||
/* -( Repository URIs )---------------------------------------------------- */
|
||||
|
||||
|
||||
public function attachURIs(array $uris) {
|
||||
$custom_map = array();
|
||||
foreach ($uris as $key => $uri) {
|
||||
$builtin_key = $uri->getRepositoryURIBuiltinKey();
|
||||
if ($builtin_key !== null) {
|
||||
$custom_map[$builtin_key] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$builtin_uris = $this->newBuiltinURIs();
|
||||
$seen_builtins = array();
|
||||
foreach ($builtin_uris as $builtin_uri) {
|
||||
$builtin_key = $builtin_uri->getRepositoryURIBuiltinKey();
|
||||
$seen_builtins[$builtin_key] = true;
|
||||
|
||||
// If this builtin URI is disabled, don't attach it and remove the
|
||||
// persisted version if it exists.
|
||||
if ($builtin_uri->getIsDisabled()) {
|
||||
if (isset($custom_map[$builtin_key])) {
|
||||
unset($uris[$custom_map[$builtin_key]]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we don't have a persisted version of the URI, add the builtin
|
||||
// version.
|
||||
if (empty($custom_map[$builtin_key])) {
|
||||
$uris[] = $builtin_uri;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any builtins which no longer exist.
|
||||
foreach ($custom_map as $builtin_key => $key) {
|
||||
if (empty($seen_builtins[$builtin_key])) {
|
||||
unset($uris[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->uris = $uris;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getURIs() {
|
||||
return $this->assertAttached($this->uris);
|
||||
}
|
||||
|
||||
protected function newBuiltinURIs() {
|
||||
$has_callsign = ($this->getCallsign() !== null);
|
||||
$has_shortname = ($this->getRepositorySlug() !== null);
|
||||
|
||||
$identifier_map = array(
|
||||
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_CALLSIGN => $has_callsign,
|
||||
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_SHORTNAME => $has_shortname,
|
||||
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_ID => true,
|
||||
);
|
||||
|
||||
$allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
|
||||
|
||||
$base_uri = PhabricatorEnv::getURI('/');
|
||||
$base_uri = new PhutilURI($base_uri);
|
||||
$has_https = ($base_uri->getProtocol() == 'https');
|
||||
$has_https = ($has_https && $allow_http);
|
||||
|
||||
$has_http = !PhabricatorEnv::getEnvConfig('security.require-https');
|
||||
$has_http = ($has_http && $allow_http);
|
||||
|
||||
// TODO: Maybe allow users to disable this by default somehow?
|
||||
$has_ssh = true;
|
||||
|
||||
$protocol_map = array(
|
||||
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH => $has_ssh,
|
||||
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS => $has_https,
|
||||
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP => $has_http,
|
||||
);
|
||||
|
||||
$uris = array();
|
||||
foreach ($protocol_map as $protocol => $proto_supported) {
|
||||
foreach ($identifier_map as $identifier => $id_supported) {
|
||||
$uris[] = PhabricatorRepositoryURI::initializeNewURI($this)
|
||||
->setBuiltinProtocol($protocol)
|
||||
->setBuiltinIdentifier($identifier)
|
||||
->setIsDisabled(!$proto_supported || !$id_supported);
|
||||
}
|
||||
}
|
||||
|
||||
return $uris;
|
||||
}
|
||||
|
||||
|
||||
/* -( Cluster Synchronization )-------------------------------------------- */
|
||||
|
||||
|
||||
private function shouldEnableSynchronization() {
|
||||
// TODO: This mostly works, but isn't stable enough for production yet.
|
||||
$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) {
|
||||
|
@ -2282,6 +2223,37 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -2310,15 +2282,15 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
if ($this_version) {
|
||||
$this_version = (int)$this_version->getRepositoryVersion();
|
||||
} else {
|
||||
$this_version = 0;
|
||||
$this_version = -1;
|
||||
}
|
||||
|
||||
if ($versions) {
|
||||
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
|
||||
} else {
|
||||
$max_version = 0;
|
||||
}
|
||||
// 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) {
|
||||
|
@ -2327,8 +2299,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Actualy fetch the newer version from one of the nodes which has
|
||||
// it.
|
||||
$this->synchronizeWorkingCopyFromDevices($fetchable);
|
||||
|
||||
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
|
||||
$repository_phid,
|
||||
|
@ -2336,16 +2307,74 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$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 $max_version;
|
||||
return $result_version;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task sync
|
||||
*/
|
||||
public function synchronizeWorkingCopyBeforeWrite() {
|
||||
public function synchronizeWorkingCopyBeforeWrite(
|
||||
PhabricatorUser $actor) {
|
||||
if (!$this->shouldEnableSynchronization()) {
|
||||
return;
|
||||
}
|
||||
|
@ -2368,18 +2397,29 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
continue;
|
||||
}
|
||||
|
||||
// TODO: This should provide more help so users can resolve the issue.
|
||||
throw new Exception(
|
||||
pht(
|
||||
'An incomplete write was previously performed to this repository; '.
|
||||
'refusing new writes.'));
|
||||
'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);
|
||||
$device_phid,
|
||||
array(
|
||||
'userPHID' => $actor->getPHID(),
|
||||
'epoch' => PhabricatorTime::getNow(),
|
||||
'devicePHID' => $device_phid,
|
||||
));
|
||||
|
||||
$this->clusterWriteVersion = $max_version;
|
||||
$this->clusterWriteLock = $write_lock;
|
||||
|
@ -2434,6 +2474,147 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
$protocol = $binding->getAlmanacPropertyValue('protocol');
|
||||
if ($protocol === null) {
|
||||
$protocol = 'https';
|
||||
}
|
||||
|
||||
$iface = $binding->getInterface();
|
||||
$address = $iface->renderDisplayAddress();
|
||||
|
||||
$path = $this->getURI();
|
||||
|
||||
return id(new PhutilURI("{$protocol}://{$address}"))
|
||||
->setPath($path);
|
||||
}
|
||||
|
||||
public function loadAlmanacService() {
|
||||
$service_phid = $this->getAlmanacServicePHID();
|
||||
if (!$service_phid) {
|
||||
// No service, so this is a local repository.
|
||||
return null;
|
||||
}
|
||||
|
||||
$service = id(new AlmanacServiceQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPHIDs(array($service_phid))
|
||||
->needBindings(true)
|
||||
->needProperties(true)
|
||||
->executeOne();
|
||||
if (!$service) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository is invalid or could not '.
|
||||
'be loaded.'));
|
||||
}
|
||||
|
||||
$service_type = $service->getServiceImplementation();
|
||||
if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository does not have the correct '.
|
||||
'service type.'));
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* -( Symbols )-------------------------------------------------------------*/
|
||||
|
||||
public function getSymbolSources() {
|
||||
|
@ -2626,4 +2807,42 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $this->spacePHID;
|
||||
}
|
||||
|
||||
/* -( PhabricatorConduitResultInterface )---------------------------------- */
|
||||
|
||||
|
||||
public function getFieldSpecificationsForConduit() {
|
||||
return array(
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('name')
|
||||
->setType('string')
|
||||
->setDescription(pht('The repository name.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('vcs')
|
||||
->setType('string')
|
||||
->setDescription(
|
||||
pht('The VCS this repository uses ("git", "hg" or "svn").')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('callsign')
|
||||
->setType('string')
|
||||
->setDescription(pht('The repository callsign, if it has one.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('shortName')
|
||||
->setType('string')
|
||||
->setDescription(pht('Unique short name, if the repository has one.')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit() {
|
||||
return array(
|
||||
'name' => $this->getName(),
|
||||
'vcs' => $this->getVersionControlSystem(),
|
||||
'callsign' => $this->getCallsign(),
|
||||
'shortName' => $this->getRepositorySlug(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getConduitSearchAttachments() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ final class PhabricatorRepositoryPushLog
|
|||
protected $epoch;
|
||||
protected $pusherPHID;
|
||||
protected $pushEventPHID;
|
||||
protected $devicePHID;
|
||||
protected $refType;
|
||||
protected $refNameHash;
|
||||
protected $refNameRaw;
|
||||
|
@ -81,6 +82,7 @@ final class PhabricatorRepositoryPushLog
|
|||
'refNew' => 'text40',
|
||||
'mergeBase' => 'text40?',
|
||||
'changeFlags' => 'uint32',
|
||||
'devicePHID' => 'phid?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_repository' => array(
|
||||
|
|
|
@ -12,16 +12,9 @@ final class PhabricatorRepositoryTransaction
|
|||
const TYPE_TRACK_ONLY = 'repo:track-only';
|
||||
const TYPE_AUTOCLOSE_ONLY = 'repo:autoclose-only';
|
||||
const TYPE_SVN_SUBPATH = 'repo:svn-subpath';
|
||||
const TYPE_UUID = 'repo:uuid';
|
||||
const TYPE_NOTIFY = 'repo:notify';
|
||||
const TYPE_AUTOCLOSE = 'repo:autoclose';
|
||||
const TYPE_REMOTE_URI = 'repo:remote-uri';
|
||||
const TYPE_LOCAL_PATH = 'repo:local-path';
|
||||
const TYPE_HOSTING = 'repo:hosting';
|
||||
const TYPE_PROTOCOL_HTTP = 'repo:serve-http';
|
||||
const TYPE_PROTOCOL_SSH = 'repo:serve-ssh';
|
||||
const TYPE_PUSH_POLICY = 'repo:push-policy';
|
||||
const TYPE_CREDENTIAL = 'repo:credential';
|
||||
const TYPE_DANGEROUS = 'repo:dangerous';
|
||||
const TYPE_SLUG = 'repo:slug';
|
||||
const TYPE_SERVICE = 'repo:service';
|
||||
|
@ -37,6 +30,13 @@ final class PhabricatorRepositoryTransaction
|
|||
const TYPE_SSH_KEYFILE = 'repo:ssh-keyfile';
|
||||
const TYPE_HTTP_LOGIN = 'repo:http-login';
|
||||
const TYPE_HTTP_PASS = 'repo:http-pass';
|
||||
const TYPE_CREDENTIAL = 'repo:credential';
|
||||
const TYPE_PROTOCOL_HTTP = 'repo:serve-http';
|
||||
const TYPE_PROTOCOL_SSH = 'repo:serve-ssh';
|
||||
const TYPE_HOSTING = 'repo:hosting';
|
||||
const TYPE_LOCAL_PATH = 'repo:local-path';
|
||||
const TYPE_REMOTE_URI = 'repo:remote-uri';
|
||||
const TYPE_UUID = 'repo:uuid';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'repository';
|
||||
|
@ -134,7 +134,13 @@ final class PhabricatorRepositoryTransaction
|
|||
'%s created this repository.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
case self::TYPE_ACTIVATE:
|
||||
if ($new) {
|
||||
// TODO: Old versions of this transaction use a boolean value, but
|
||||
// should be migrated.
|
||||
$is_deactivate =
|
||||
(!$new) ||
|
||||
($new == PhabricatorRepository::STATUS_INACTIVE);
|
||||
|
||||
if (!$is_deactivate) {
|
||||
return pht(
|
||||
'%s activated this repository.',
|
||||
$this->renderHandleLink($author_phid));
|
||||
|
|
301
src/applications/repository/storage/PhabricatorRepositoryURI.php
Normal file
301
src/applications/repository/storage/PhabricatorRepositoryURI.php
Normal file
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorRepositoryURI
|
||||
extends PhabricatorRepositoryDAO {
|
||||
|
||||
protected $repositoryPHID;
|
||||
protected $uri;
|
||||
protected $builtinProtocol;
|
||||
protected $builtinIdentifier;
|
||||
protected $credentialPHID;
|
||||
protected $ioType;
|
||||
protected $displayType;
|
||||
protected $isDisabled;
|
||||
|
||||
private $repository = self::ATTACHABLE;
|
||||
|
||||
const BUILTIN_PROTOCOL_SSH = 'ssh';
|
||||
const BUILTIN_PROTOCOL_HTTP = 'http';
|
||||
const BUILTIN_PROTOCOL_HTTPS = 'https';
|
||||
|
||||
const BUILTIN_IDENTIFIER_ID = 'id';
|
||||
const BUILTIN_IDENTIFIER_SHORTNAME = 'shortname';
|
||||
const BUILTIN_IDENTIFIER_CALLSIGN = 'callsign';
|
||||
|
||||
const DISPLAY_DEFAULT = 'default';
|
||||
const DISPLAY_NEVER = 'never';
|
||||
const DISPLAY_ALWAYS = 'always';
|
||||
|
||||
const IO_DEFAULT = 'default';
|
||||
const IO_OBSERVE = 'observe';
|
||||
const IO_MIRROR = 'mirror';
|
||||
const IO_NONE = 'none';
|
||||
const IO_READ = 'read';
|
||||
const IO_READWRITE = 'readwrite';
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'uri' => 'text255',
|
||||
'builtinProtocol' => 'text32?',
|
||||
'builtinIdentifier' => 'text32?',
|
||||
'credentialPHID' => 'phid?',
|
||||
'ioType' => 'text32',
|
||||
'displayType' => 'text32',
|
||||
'isDisabled' => 'bool',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_builtin' => array(
|
||||
'columns' => array(
|
||||
'repositoryPHID',
|
||||
'builtinProtocol',
|
||||
'builtinIdentifier',
|
||||
),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public static function initializeNewURI(PhabricatorRepository $repository) {
|
||||
return id(new self())
|
||||
->attachRepository($repository)
|
||||
->setRepositoryPHID($repository->getPHID())
|
||||
->setIoType(self::IO_DEFAULT)
|
||||
->setDisplayType(self::DISPLAY_DEFAULT)
|
||||
->setIsDisabled(0);
|
||||
}
|
||||
|
||||
public function attachRepository(PhabricatorRepository $repository) {
|
||||
$this->repository = $repository;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepository() {
|
||||
return $this->assertAttached($this->repository);
|
||||
}
|
||||
|
||||
public function getRepositoryURIBuiltinKey() {
|
||||
if (!$this->getBuiltinProtocol()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = array(
|
||||
$this->getBuiltinProtocol(),
|
||||
$this->getBuiltinIdentifier(),
|
||||
);
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
public function isBuiltin() {
|
||||
return (bool)$this->getBuiltinProtocol();
|
||||
}
|
||||
|
||||
public function getEffectiveDisplayType() {
|
||||
$display = $this->getDisplayType();
|
||||
|
||||
if ($display != self::IO_DEFAULT) {
|
||||
return $display;
|
||||
}
|
||||
|
||||
switch ($this->getEffectiveIOType()) {
|
||||
case self::IO_MIRROR:
|
||||
case self::IO_OBSERVE:
|
||||
return self::DISPLAY_NEVER;
|
||||
case self::IO_NONE:
|
||||
if ($this->isBuiltin()) {
|
||||
return self::DISPLAY_NEVER;
|
||||
} else {
|
||||
return self::DISPLAY_ALWAYS;
|
||||
}
|
||||
case self::IO_READ:
|
||||
case self::IO_READWRITE:
|
||||
// By default, only show the "best" version of the builtin URI, not the
|
||||
// other redundant versions.
|
||||
if ($this->isBuiltin()) {
|
||||
$repository = $this->getRepository();
|
||||
$other_uris = $repository->getURIs();
|
||||
|
||||
$identifier_value = array(
|
||||
self::BUILTIN_IDENTIFIER_CALLSIGN => 3,
|
||||
self::BUILTIN_IDENTIFIER_SHORTNAME => 2,
|
||||
self::BUILTIN_IDENTIFIER_ID => 1,
|
||||
);
|
||||
|
||||
$have_identifiers = array();
|
||||
foreach ($other_uris as $other_uri) {
|
||||
if ($other_uri->getIsDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$identifier = $other_uri->getBuiltinIdentifier();
|
||||
if (!$identifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getEffectiveIOType() {
|
||||
$io = $this->getIoType();
|
||||
|
||||
if ($io != self::IO_DEFAULT) {
|
||||
return $io;
|
||||
}
|
||||
|
||||
if ($this->isBuiltin()) {
|
||||
$repository = $this->getRepository();
|
||||
$other_uris = $repository->getURIs();
|
||||
|
||||
$any_observe = false;
|
||||
foreach ($other_uris as $other_uri) {
|
||||
if ($other_uri->getIoType() == self::IO_OBSERVE) {
|
||||
$any_observe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($any_observe) {
|
||||
return self::IO_READ;
|
||||
} else {
|
||||
return self::IO_READWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
return self::IO_IGNORE;
|
||||
}
|
||||
|
||||
|
||||
public function getDisplayURI() {
|
||||
$uri = new PhutilURI($this->getURI());
|
||||
|
||||
$protocol = $this->getForcedProtocol();
|
||||
if ($protocol) {
|
||||
$uri->setProtocol($protocol);
|
||||
}
|
||||
|
||||
$user = $this->getForcedUser();
|
||||
if ($user) {
|
||||
$uri->setUser($user);
|
||||
}
|
||||
|
||||
$host = $this->getForcedHost();
|
||||
if ($host) {
|
||||
$uri->setDomain($host);
|
||||
}
|
||||
|
||||
$port = $this->getForcedPort();
|
||||
if ($port) {
|
||||
$uri->setPort($port);
|
||||
}
|
||||
|
||||
$path = $this->getForcedPath();
|
||||
if ($path) {
|
||||
$uri->setPath($path);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function getForcedProtocol() {
|
||||
switch ($this->getBuiltinProtocol()) {
|
||||
case self::BUILTIN_PROTOCOL_SSH:
|
||||
return 'ssh';
|
||||
case self::BUILTIN_PROTOCOL_HTTP:
|
||||
return 'http';
|
||||
case self::BUILTIN_PROTOCOL_HTTPS:
|
||||
return 'https';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getForcedUser() {
|
||||
switch ($this->getBuiltinProtocol()) {
|
||||
case self::BUILTIN_PROTOCOL_SSH:
|
||||
return AlmanacKeys::getClusterSSHUser();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getForcedHost() {
|
||||
$phabricator_uri = PhabricatorEnv::getURI('/');
|
||||
$phabricator_uri = new PhutilURI($phabricator_uri);
|
||||
|
||||
$phabricator_host = $phabricator_uri->getDomain();
|
||||
|
||||
switch ($this->getBuiltinProtocol()) {
|
||||
case self::BUILTIN_PROTOCOL_SSH:
|
||||
$ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host');
|
||||
if ($ssh_host !== null) {
|
||||
return $ssh_host;
|
||||
}
|
||||
return $phabricator_host;
|
||||
case self::BUILTIN_PROTOCOL_HTTP:
|
||||
case self::BUILTIN_PROTOCOL_HTTPS:
|
||||
return $phabricator_host;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getForcedPort() {
|
||||
switch ($this->getBuiltinProtocol()) {
|
||||
case self::BUILTIN_PROTOCOL_SSH:
|
||||
return PhabricatorEnv::getEnvConfig('diffusion.ssh-port');
|
||||
case self::BUILTIN_PROTOCOL_HTTP:
|
||||
case self::BUILTIN_PROTOCOL_HTTPS:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getForcedPath() {
|
||||
if (!$this->isBuiltin()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$id = $repository->getID();
|
||||
$callsign = $repository->getCallsign();
|
||||
$short_name = $repository->getRepositorySlug();
|
||||
|
||||
$clone_name = $repository->getCloneName();
|
||||
|
||||
if ($repository->isGit()) {
|
||||
$suffix = '.git';
|
||||
} else if ($repository->isHg()) {
|
||||
$suffix = '/';
|
||||
} else {
|
||||
$suffix = '';
|
||||
}
|
||||
|
||||
switch ($this->getBuiltinIdentifier()) {
|
||||
case self::BUILTIN_IDENTIFIER_ID:
|
||||
return "/diffusion/{$id}/{$clone_name}{$suffix}";
|
||||
case self::BUILTIN_IDENTIFIER_SHORTNAME:
|
||||
return "/source/{$short_name}{$suffix}";
|
||||
case self::BUILTIN_IDENTIFIER_CALLSIGN:
|
||||
return "/diffusion/{$callsign}/{$clone_name}{$suffix}";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
protected $devicePHID;
|
||||
protected $repositoryVersion;
|
||||
protected $isWriting;
|
||||
protected $writeProperties;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
|
@ -14,6 +15,7 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'repositoryVersion' => 'uint32',
|
||||
'isWriting' => 'bool',
|
||||
'writeProperties' => 'text?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_workingcopy' => array(
|
||||
|
@ -66,7 +68,10 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
* lock is released by default. This is a durable lock which stays locked
|
||||
* by default.
|
||||
*/
|
||||
public static function willWrite($repository_phid, $device_phid) {
|
||||
public static function willWrite(
|
||||
$repository_phid,
|
||||
$device_phid,
|
||||
array $write_properties) {
|
||||
$version = new self();
|
||||
$conn_w = $version->establishConnection('w');
|
||||
$table = $version->getTableName();
|
||||
|
@ -74,16 +79,19 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
queryfx(
|
||||
$conn_w,
|
||||
'INSERT INTO %T
|
||||
(repositoryPHID, devicePHID, repositoryVersion, isWriting)
|
||||
(repositoryPHID, devicePHID, repositoryVersion, isWriting,
|
||||
writeProperties)
|
||||
VALUES
|
||||
(%s, %s, %d, %d)
|
||||
(%s, %s, %d, %d, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
isWriting = VALUES(isWriting)',
|
||||
isWriting = VALUES(isWriting),
|
||||
writeProperties = VALUES(writeProperties)',
|
||||
$table,
|
||||
$repository_phid,
|
||||
$device_phid,
|
||||
0,
|
||||
1,
|
||||
1);
|
||||
phutil_json_encode($write_properties));
|
||||
}
|
||||
|
||||
|
||||
|
@ -101,7 +109,9 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET repositoryVersion = %d, isWriting = 0
|
||||
'UPDATE %T SET
|
||||
repositoryVersion = %d,
|
||||
isWriting = 0
|
||||
WHERE
|
||||
repositoryPHID = %s AND
|
||||
devicePHID = %s AND
|
||||
|
@ -122,6 +132,7 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
$repository_phid,
|
||||
$device_phid,
|
||||
$new_version) {
|
||||
|
||||
$version = new self();
|
||||
$conn_w = $version->establishConnection('w');
|
||||
$table = $version->getTableName();
|
||||
|
@ -142,4 +153,23 @@ final class PhabricatorRepositoryWorkingCopyVersion
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Explicitly demote a device.
|
||||
*/
|
||||
public static function demoteDevice(
|
||||
$repository_phid,
|
||||
$device_phid) {
|
||||
|
||||
$version = new self();
|
||||
$conn_w = $version->establishConnection('w');
|
||||
$table = $version->getTableName();
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'DELETE FROM %T WHERE repositoryPHID = %s AND devicePHID = %s',
|
||||
$table,
|
||||
$repository_phid,
|
||||
$device_phid);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -147,7 +147,8 @@ final class PhabricatorRepositoryTestCase
|
|||
);
|
||||
|
||||
foreach ($map as $input => $expect) {
|
||||
$actual = PhabricatorRepository::filterMercurialDebugOutput($input);
|
||||
$actual = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
|
||||
$input);
|
||||
$this->assertEqual($expect, $actual, $input);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker
|
|||
)),
|
||||
'diffusion.querycommits',
|
||||
array(
|
||||
'repositoryPHID' => $repository->getPHID(),
|
||||
'phids' => array($commit->getPHID()),
|
||||
'bypassCache' => true,
|
||||
'needMessages' => true,
|
||||
|
|
|
@ -37,7 +37,10 @@ abstract class PhabricatorSearchEngineAPIMethod
|
|||
}
|
||||
|
||||
public function getMethodStatusDescription() {
|
||||
return pht('ApplicationSearch methods are highly unstable.');
|
||||
return pht(
|
||||
'ApplicationSearch methods are fairly stable, but were introduced '.
|
||||
'relatively recently and may continue to evolve as more applications '.
|
||||
'adopt them.');
|
||||
}
|
||||
|
||||
final protected function defineParamTypes() {
|
||||
|
|
|
@ -152,7 +152,7 @@ final class PhabricatorSlowvoteEditController
|
|||
->setValue($v_description))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setLabel(pht('Tags'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()));
|
||||
|
|
|
@ -16,7 +16,10 @@ abstract class PhabricatorEditEngineAPIMethod
|
|||
}
|
||||
|
||||
public function getMethodStatusDescription() {
|
||||
return pht('ApplicationEditor methods are highly unstable.');
|
||||
return pht(
|
||||
'ApplicationEditor methods are fairly stable, but were introduced '.
|
||||
'relatively recently and may continue to evolve as more applications '.
|
||||
'adopt them.');
|
||||
}
|
||||
|
||||
final protected function defineParamTypes() {
|
||||
|
|
|
@ -99,8 +99,15 @@ class PhabricatorApplicationTransactionFeedStory
|
|||
}
|
||||
}
|
||||
|
||||
$view->setImage(
|
||||
$this->getHandle($xaction->getAuthorPHID())->getImageURI());
|
||||
$author_phid = $xaction->getAuthorPHID();
|
||||
$author_handle = $this->getHandle($author_phid);
|
||||
$author_image = $author_handle->getImageURI();
|
||||
|
||||
if ($author_image) {
|
||||
$view->setImage($author_image);
|
||||
} else {
|
||||
$view->setAuthorIcon($author_handle->getIcon());
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
|
|
@ -1227,7 +1227,17 @@ abstract class PhabricatorApplicationTransaction
|
|||
// Make this weaker than TYPE_COMMENT.
|
||||
return 0.25;
|
||||
}
|
||||
break;
|
||||
|
||||
if ($this->isApplicationAuthor()) {
|
||||
// When applications (most often: Herald) change subscriptions it
|
||||
// is very uninteresting.
|
||||
return 0.000000001;
|
||||
}
|
||||
|
||||
// In other cases, subscriptions are more interesting than comments
|
||||
// (which are shown anyway) but less interesting than any other type of
|
||||
// transaction.
|
||||
return 0.75;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
|
@ -1462,6 +1472,14 @@ abstract class PhabricatorApplicationTransaction
|
|||
return true;
|
||||
}
|
||||
|
||||
private function isApplicationAuthor() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
$author_type = phid_get_type($author_phid);
|
||||
$application_type = PhabricatorApplicationApplicationPHIDType::TYPECONST;
|
||||
return ($author_type == $application_type);
|
||||
}
|
||||
|
||||
|
||||
private function getInterestingMoves(array $moves) {
|
||||
// Remove moves which only shift the position of a task within a column.
|
||||
foreach ($moves as $key => $move) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@title Cluster: Daemons
|
||||
@group intro
|
||||
@group cluster
|
||||
|
||||
Configuring Phabricator to use multiple daemon hosts.
|
||||
|
||||
|
|
|
@ -1,36 +1,81 @@
|
|||
@title Cluster: Databases
|
||||
@group intro
|
||||
@group cluster
|
||||
|
||||
Configuring Phabricator to use multiple database hosts.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
WARNING: This feature is a very early prototype; the features this document
|
||||
describes are mostly speculative fantasy.
|
||||
|
||||
You can deploy Phabricator with multiple database hosts, configured as a master
|
||||
and a set of replicas. The advantages of doing this are:
|
||||
|
||||
- faster recovery from disasters by promoting a replica;
|
||||
- graceful degradation if the master fails;
|
||||
- reduced load on the master; and
|
||||
- graceful degradation if the master fails; and
|
||||
- some tools to help monitor and manage replica health.
|
||||
|
||||
This configuration is complex, and many installs do not need to pursue it.
|
||||
|
||||
Phabricator can not currently be configured into a multi-master mode, nor can
|
||||
it be configured to automatically promote a replica to become the new master.
|
||||
|
||||
If you lose the master, Phabricator can degrade automatically into read-only
|
||||
mode and remain available, but can not fully recover without operational
|
||||
intervention unless the master recovers on its own.
|
||||
|
||||
Phabricator will not currently send read traffic to replicas unless the master
|
||||
has failed, so configuring a replica will not currently spread any load away
|
||||
from the master. Future versions of Phabricator are expected to be able to
|
||||
distribute some read traffic to replicas.
|
||||
|
||||
Phabricator can not currently be configured into a multi-master mode, nor can
|
||||
it be configured to automatically promote a replica to become the new master.
|
||||
There are no current plans to support multi-master mode or autonomous failover,
|
||||
although this may change in the future.
|
||||
|
||||
|
||||
Setting up MySQL Replication
|
||||
============================
|
||||
|
||||
TODO: Write this section.
|
||||
To begin, set up a replica database server and configure MySQL replication.
|
||||
|
||||
If you aren't sure how to do this, refer to the MySQL manual for instructions.
|
||||
The MySQL documentation is comprehensive and walks through the steps and
|
||||
options in good detail. You should understand MySQL replication before
|
||||
deploying it in production: Phabricator layers on top of it, and does not
|
||||
attempt to abstract it away.
|
||||
|
||||
Some useful notes for configuring replication for Phabricator:
|
||||
|
||||
**Binlog Format**: Phabricator issues some queries which MySQL will detect as
|
||||
unsafe if you use the `STATEMENT` binlog format (the default). Instead, use
|
||||
`MIXED` (recommended) or `ROW` as the `binlog_format`.
|
||||
|
||||
**Grant `REPLICATION CLIENT` Privilege**: If you give the user that Phabricator
|
||||
will use to connect to the replica database server the `REPLICATION CLIENT`
|
||||
privilege, Phabricator's status console can give you more information about
|
||||
replica health and state.
|
||||
|
||||
**Copying Data to Replicas**: Phabricator currently uses a mixture of MyISAM
|
||||
and InnoDB tables, so it can be difficult to guarantee that a dump is wholly
|
||||
consistent and suitable for loading into a replica because MySQL uses different
|
||||
consistency mechanisms for the different storage engines.
|
||||
|
||||
An approach you may want to consider to limit downtime but still produce a
|
||||
consistent dump is to leave Phabricator running but configured in read-only
|
||||
mode while dumping:
|
||||
|
||||
- Stop all the daemons.
|
||||
- Set `cluster.read-only` to `true` and deploy the new configuration. The
|
||||
web UI should now show that Phabricator is in "Read Only" mode.
|
||||
- Dump the database. You can do this with `bin/storage dump --for-replica`
|
||||
to add the `--master-data` flag to the underlying command and include a
|
||||
`CHANGE MASTER ...` statement in the dump.
|
||||
- Once the dump finishes, turn `cluster.read-only` off again to restore
|
||||
service. Continue loading the dump into the replica normally.
|
||||
|
||||
**Log Expiration**: You can configure MySQL to automatically clean up old
|
||||
binary logs on startup with the `expire_logs_days` option. If you do not
|
||||
configure this and do not explicitly purge old logs with `PURGE BINARY LOGS`,
|
||||
the binary logs on disk will grow unboundedly and relatively quickly.
|
||||
|
||||
Once you have a working replica, continue below to tell Phabricator about it.
|
||||
|
||||
|
||||
Configuring Replicas
|
||||
|
@ -207,7 +252,38 @@ the new master. See the next section, "Promoting a Replica", for details.
|
|||
Promoting a Replica
|
||||
===================
|
||||
|
||||
TODO: Write this section.
|
||||
If you lose access to the master database, Phabricator will degrade into
|
||||
read-only mode. This is described in greater detail below.
|
||||
|
||||
The easiest way to get out of read-only mode is to restore the master database.
|
||||
If the database recovers on its own or operations staff can revive it,
|
||||
Phabricator will return to full working order after a few moments.
|
||||
|
||||
If you can't restore the master or are unsure you will be able to restore the
|
||||
master quickly, you can promote a replica to become the new master instead.
|
||||
|
||||
Before doing this, you should first assess how far behind the master the
|
||||
replica was when the link died. Any data which was not replicated will either
|
||||
be lost or become very difficult to recover after you promote a replica.
|
||||
|
||||
For example, if some `T1234` had been created on the master but had not yet
|
||||
replicated and you promote the replica, a new `T1234` may be created on the
|
||||
replica after promotion. Even if you can recover the master later, merging
|
||||
the data will be difficult because each database may have conflicting changes
|
||||
which can not be merged easily.
|
||||
|
||||
If there was a significant replication delay at the time of the failure, you
|
||||
may wait to try harder or spend more time attempting to recover the master
|
||||
before choosing to promote.
|
||||
|
||||
If you have made a choice to promote, disable replication on the replica and
|
||||
mark it as the `master` in `cluster.databases`. Remove the original master and
|
||||
deploy the configuration change to all surviving hosts.
|
||||
|
||||
Once write service is restored, you should provision, deploy, and configure a
|
||||
new replica by following the steps you took the first time around. You are
|
||||
critically vulnerable to a second disruption until you have restored the
|
||||
redundancy.
|
||||
|
||||
|
||||
Unreachable Masters
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@title Cluster: Notifications
|
||||
@group intro
|
||||
@group cluster
|
||||
|
||||
Configuring Phabricator to use multiple notification servers.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@title Cluster: Repositories
|
||||
@group intro
|
||||
@group cluster
|
||||
|
||||
Configuring Phabricator to use multiple repository hosts.
|
||||
|
||||
|
@ -19,19 +19,19 @@ advantages of doing this are:
|
|||
|
||||
This configuration is complex, and many installs do not need to pursue it.
|
||||
|
||||
This configuration is not currently supported with Subversion.
|
||||
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. If you make repositories available over SSH, they must
|
||||
also run a properly configured `sshd`.
|
||||
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.
|
||||
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
|
||||
|
@ -57,6 +57,17 @@ If it isn't, they block the read until they can complete a fetch.
|
|||
Before responding to a write, replicas obtain a global lock, perform the same
|
||||
version check and fetch if necessary, then allow the write to continue.
|
||||
|
||||
Additionally, repositories passively check other nodes for updates and
|
||||
replicate changes in the background. After you push a change to a repositroy,
|
||||
it will usually spread passively to all other repository nodes within a few
|
||||
minutes.
|
||||
|
||||
Even if passive replication is slow, the active replication makes acknowledged
|
||||
changes sequential to all observers: after a write is acknowledged, all
|
||||
subsequent reads are guaranteed to see it. The system does not permit stale
|
||||
reads, and you do not need to wait for a replication delay to see a consistent
|
||||
view of the repository no matter which node you ask.
|
||||
|
||||
|
||||
HTTP vs HTTPS
|
||||
=============
|
||||
|
@ -84,6 +95,237 @@ 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.
|
||||
|
||||
|
||||
Monitoring Services
|
||||
===================
|
||||
|
||||
You can get an overview of repository cluster status from the
|
||||
{nav Config > Repository Servers} screen. This table shows a high-level
|
||||
overview of all active repository services.
|
||||
|
||||
**Repos**: The number of repositories hosted on this service.
|
||||
|
||||
**Sync**: Synchronization status of repositories on this service. This is an
|
||||
at-a-glance view of service health, and can show these values:
|
||||
|
||||
- **Synchronized**: All nodes are fully synchronized and have the latest
|
||||
version of all repositories.
|
||||
- **Partial**: All repositories either have at least two leaders, or have
|
||||
a very recent write which is not expected to have propagated yet.
|
||||
- **Unsynchronized**: At least one repository has changes which are
|
||||
only available on one node and were not pushed very recently. Data may
|
||||
be at risk.
|
||||
- **No Repositories**: This service has no repositories.
|
||||
- **Ambiguous Leader**: At least one repository has an ambiguous leader.
|
||||
|
||||
If this screen identifies problems, you can drill down into repository details
|
||||
to get more information about them. See the next section for details.
|
||||
|
||||
|
||||
Monitoring Repositories
|
||||
=======================
|
||||
|
||||
You can get a more detailed view the current status of a specific repository on
|
||||
cluster devices in {nav Diffusion > (Repository) > Manage Repository > Cluster
|
||||
Configuration}.
|
||||
|
||||
This screen shows all the configured devices which are hosting the repository
|
||||
and the available version on that device.
|
||||
|
||||
**Version**: When a repository is mutated by a push, Phabricator increases
|
||||
an internal version number for the repository. This column shows which version
|
||||
is on disk on the corresponding device.
|
||||
|
||||
After a change is pushed, the device which received the change will have a
|
||||
larger version number than the other devices. The change should be passively
|
||||
replicated to the remaining devices after a brief period of time, although this
|
||||
can take a while if the change was large or the network connection between
|
||||
devices is slow or unreliable.
|
||||
|
||||
You can click the version number to see the corresponding push logs for that
|
||||
change. The logs contain details about what was changed, and can help you
|
||||
identify if replication is slow because a change is large or for some other
|
||||
reason.
|
||||
|
||||
**Writing**: This shows that the device is currently holding a write lock. This
|
||||
normally means that it is actively receiving a push, but can also mean that
|
||||
there was a write interruption. See "Write Interruptions" below for details.
|
||||
|
||||
**Last Writer**: This column identifies the user who most recently pushed a
|
||||
change to this device. If the write lock is currently held, this user is
|
||||
the user whose change is holding the lock.
|
||||
|
||||
**Last Write At**: When the most recent write started. If the write lock is
|
||||
currently held, this shows when the lock was acquired.
|
||||
|
||||
|
||||
|
||||
|
||||
Cluster Failure Modes
|
||||
=====================
|
||||
|
||||
There are three major cluster failure modes:
|
||||
|
||||
- **Write Interruptions**: A write started but did not complete, leaving
|
||||
the disk state and cluster state out of sync.
|
||||
- **Loss of Leaders**: None of the devices with the most up-to-date data
|
||||
are reachable.
|
||||
- **Ambiguous Leaders**: The internal state of the repository is unclear.
|
||||
|
||||
Phabricator can detect these issues, and responds by freezing the repository
|
||||
(usually preventing all reads and writes) until the issue is resolved. These
|
||||
conditions are normally rare and very little data is at risk, but Phabricator
|
||||
errs on the side of caution and requires decisions which may result in data
|
||||
loss to be confirmed by a human.
|
||||
|
||||
The next sections cover these failure modes and appropriate responses in
|
||||
more detail. In general, you will respond to these issues by assessing the
|
||||
situation and then possibly choosing to discard some data.
|
||||
|
||||
|
||||
Write Interruptions
|
||||
===================
|
||||
|
||||
A repository cluster can be put into an inconsistent state by an interruption
|
||||
in a brief window during and immediately after a write. This looks like this:
|
||||
|
||||
- A change is pushed to a server.
|
||||
- The server acquires a write lock and begins writing the change.
|
||||
- During or immediately after the write, lightning strikes the server
|
||||
and destroys it.
|
||||
|
||||
Phabricator can not commit changes to a working copy (stored on disk) and to
|
||||
the global state (stored in a database) atomically, so there is necessarily a
|
||||
narrow window between committing these two different states when some tragedy
|
||||
can befall a server, leaving the global and local views of the repository state
|
||||
possibly divergent.
|
||||
|
||||
In these cases, Phabricator fails into a frozen state where further writes
|
||||
are not permitted until the failure is investigated and resolved. When a
|
||||
repository is frozen in this way it remains readable.
|
||||
|
||||
You can use the monitoring console to review the state of a frozen repository
|
||||
with a held write lock. The **Writing** column will show which device is
|
||||
holding the lock, and whoever is named in the **Last Writer** column may be
|
||||
able to help you figure out what happened by providing more information about
|
||||
what they were doing and what they observed.
|
||||
|
||||
Because the push was not acknowledged, it is normally safe to resolve this
|
||||
issue by demoting the device. Demoting the device will undo any changes
|
||||
committed by the push, and they will be lost forever.
|
||||
|
||||
However, the user should have received an error anyway, and should not expect
|
||||
their push to have worked. Still, data is technically at risk and you may want
|
||||
to investigate further and try to understand the issue in more detail before
|
||||
continuing.
|
||||
|
||||
There is no way to explicitly keep the write, but if it was committed to disk
|
||||
you can recover it manually from the working copy on the device (for example,
|
||||
by using `git format-patch`) and then push it again after recovering.
|
||||
|
||||
If you demote the device, the in-process write will be thrown away, even if it
|
||||
was complete on disk. To demote the device and release the write lock, run this
|
||||
command:
|
||||
|
||||
```
|
||||
phabricator/ $ ./bin/repository thaw <repository> --demote <device>
|
||||
```
|
||||
|
||||
{icon exclamation-triangle, color="yellow"} Any committed but unacknowledged
|
||||
data on the device will be lost.
|
||||
|
||||
|
||||
Loss of Leaders
|
||||
===============
|
||||
|
||||
A more straightforward failure condition is the loss of all servers in a
|
||||
cluster which have the most up-to-date copy of a repository. This looks like
|
||||
this:
|
||||
|
||||
- There is a cluster setup with two devices, X and Y.
|
||||
- A new change is pushed to server X.
|
||||
- Before the change can propagate to server Y, lightning strikes server X
|
||||
and destroys it.
|
||||
|
||||
Here, all of the "leader" devices with the most up-to-date copy of the
|
||||
repository have been lost. Phabricator will freeze the repository refuse to
|
||||
serve requests because it can not serve it consistently, and can not accept new
|
||||
writes without data loss.
|
||||
|
||||
The most straightforward way to resolve this issue is to restore any leader to
|
||||
service. The change will be able to replicate to other devices once a leader
|
||||
comes back online.
|
||||
|
||||
If you are unable to restore a leader or unsure that you can restore one
|
||||
quickly, you can use the monitoring console to review which changes are
|
||||
present on the leaders but not present on the followers by examining the
|
||||
push logs.
|
||||
|
||||
If you are comfortable discarding these changes, you can instruct Phabricator
|
||||
that it can forget about the leaders in two ways: disable the service bindings
|
||||
to all of the leader devices so they are no longer part of the cluster, or use
|
||||
`bin/repository thaw` to `--demote` the leaders explicitly.
|
||||
|
||||
If you do this, **you will lose data**. Either action will discard any changes
|
||||
on the affected leaders which have not replicated to other devices in the
|
||||
cluster.
|
||||
|
||||
To remove a device from the cluster, disable all of the bindings to it
|
||||
in Almanac, using the web UI.
|
||||
|
||||
{icon exclamation-triangle, color="red"} Any data which is only present on
|
||||
the disabled device will be lost.
|
||||
|
||||
To demote a device without removing it from the cluster, run this command:
|
||||
|
||||
```
|
||||
phabricator/ $ ./bin/repository thaw rXYZ --demote repo002.corp.net
|
||||
```
|
||||
|
||||
{icon exclamation-triangle, color="red"} Any data which is only present on
|
||||
**this** device will be lost.
|
||||
|
||||
|
||||
Ambiguous Leaders
|
||||
=================
|
||||
|
||||
Repository clusters can also freeze if the leader devices are ambiguous. This
|
||||
can happen if you replace an entire cluster with new devices suddenly, or
|
||||
make a mistake with the `--demote` flag. This generally arises from some kind
|
||||
of operator error, like this:
|
||||
|
||||
- Someone accidentally uses `bin/repository thaw ... --demote` to demote
|
||||
every device in a cluster.
|
||||
- Someone accidentally deletes all the version information for a repository
|
||||
from the database by making a mistake with a `DELETE` or `UPDATE` query.
|
||||
- Someone accidentally disable all of the devices in a cluster, then add
|
||||
entirely new ones before repositories can propagate.
|
||||
|
||||
When Phabricator can not tell which device in a cluster is a leader, it freezes
|
||||
the cluster because it is possible that some devices have less data and others
|
||||
have more, and if it choses a leader arbitrarily it may destroy some data
|
||||
which you would prefer to retain.
|
||||
|
||||
To resolve this, you need to tell Phabricator which device has the most
|
||||
up-to-date data and promote that device to become a leader. If you know all
|
||||
devices have the same data, you are free to promote any device.
|
||||
|
||||
If you promote a device, **you may lose data** if you promote the wrong device
|
||||
and some other device really had more up-to-date data. If you want to double
|
||||
check, you can examine the working copies on disk before promoting by
|
||||
connecting to the machines and using commands like `git log` to inspect state.
|
||||
|
||||
Once you have identified a device which has data you're happy with, use
|
||||
`bin/repository thaw` to `--promote` the device. The data on the chosen
|
||||
device will become authoritative:
|
||||
|
||||
```
|
||||
phabricator/ $ ./bin/repository thaw rXYZ --promote repo002.corp.net
|
||||
```
|
||||
|
||||
{icon exclamation-triangle, color="red"} Any data which is only present on
|
||||
**other** devices will be lost.
|
||||
|
||||
|
||||
Backups
|
||||
======
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@title Cluster: Web Servers
|
||||
@group intro
|
||||
@group cluster
|
||||
|
||||
Configuring Phabricator to use multiple web servers.
|
||||
|
||||
|
|
|
@ -65,6 +65,13 @@ Continue to "Configuring Phabricator", below.
|
|||
Approach: CloudFlare
|
||||
========
|
||||
|
||||
WARNING: You should review all your CloudFlare settings, and be very
|
||||
sure to turn off all JavaScript, HTML, CSS minification and
|
||||
optimization features, including systems like "Rocket Loader". These
|
||||
features will break Phabricator in strange and mysterious ways that
|
||||
are unpredictable. Only allow CloudFlare to cache files, and never
|
||||
optimize them.
|
||||
|
||||
[[ https://cloudflare.com | CloudFlare ]] is a general-purpose CDN service.
|
||||
|
||||
To set up CloudFlare, you'll need to register a second domain and go through
|
||||
|
|
47
src/docs/user/userguide/diffusion_uris.diviner
Normal file
47
src/docs/user/userguide/diffusion_uris.diviner
Normal file
|
@ -0,0 +1,47 @@
|
|||
@title Diffusion User Guide: URIs
|
||||
@group userguide
|
||||
|
||||
Guide to configuring repository URIs for fetching, cloning and mirroring.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
WARNING: This document describes a feature which is still under development,
|
||||
and is not necessarily accurate or complete.
|
||||
|
||||
Phabricator can host, observe, mirror, and proxy repositories. For example,
|
||||
here are some supported use cases:
|
||||
|
||||
**Host Repositories**: Phabricator can host repositories locally. Phabricator
|
||||
maintains the writable master version of the repository, and you can push and
|
||||
pull the repository. This is the most straightforward kind of repository
|
||||
configuration, and similar to repositories on other services like GitHub or
|
||||
Bitbucket.
|
||||
|
||||
**Observe Repositories**: Phabricator can create a copy of an repository which
|
||||
is hosted elsewhere (like GitHub or Bitbucket) and track updates to the remote
|
||||
repository. This will create a read-only copy of the repository in Phabricator.
|
||||
|
||||
**Mirror Repositories**: Phabricator can publish any repository to mirrors,
|
||||
updating the mirrors as changes are made to the repository. This works with
|
||||
both local hosted repositories and remote repositories that Phabricator is
|
||||
observing.
|
||||
|
||||
**Proxy Repositories**: If you are observing a repository, you can allow users
|
||||
to read Phabricator's copy of the repository. Phabricator supports granular
|
||||
read permissions, so this can let you open a private repository up a little
|
||||
bit in a flexible way.
|
||||
|
||||
**Import Repositories**: If you have a repository elsewhere that you want to
|
||||
host on Phabricator, you can observe the remote repository first, then turn
|
||||
the tracking off once the repository fully synchronizes. This allows you to
|
||||
copy an existing repository and begin hosting it in Phabricator.
|
||||
|
||||
You can also import repositories by creating an empty hosted repository and
|
||||
then pushing everything to the repository directly.
|
||||
|
||||
You configure the behavior of a Phabricator repository by adding and
|
||||
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 expose to users, you configure the read, write, and mirroring behavior
|
||||
of the repository.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue