1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-30 01:10:58 +01:00

Merge branch 'master' of github.com:facebook/phabricator

This commit is contained in:
Chad Little 2013-10-29 16:13:49 -07:00
commit 00d9aae894
66 changed files with 2049 additions and 1092 deletions

1
bin/ssh-auth-key Symbolic link
View file

@ -0,0 +1 @@
../scripts/ssh/ssh-auth-key.php

View file

@ -559,13 +559,6 @@ return array(
// to have.
'auth.sessions.conduit' => 5,
// Set this true to enable the Settings -> SSH Public Keys panel, which will
// allow users to associated SSH public keys with their accounts. This is only
// really useful if you're setting up services over SSH and want to use
// Phabricator for authentication; in most situations you can leave this
// disabled.
'auth.sshkeys.enabled' => false,
// If true, email addresses must be verified (by clicking a link in an
// email) before a user can login. By default, verification is optional
// unless 'auth.email-domains' is nonempty (see below).

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_repository.repository
ADD COLUMN pushPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin;
UPDATE {$NAMESPACE}_repository.repository
SET pushPolicy = 'users' WHERE pushPolicy = '';

View file

@ -0,0 +1,8 @@
ALTER TABLE {$NAMESPACE}_repository.repository_commit
ADD COLUMN importStatus INT UNSIGNED NOT NULL COLLATE utf8_bin;
UPDATE {$NAMESPACE}_repository.repository_commit
SET importStatus = 15;
ALTER TABLE {$NAMESPACE}_repository.repository_commit
ADD KEY (repositoryID, importStatus);

View file

@ -0,0 +1,8 @@
#!/bin/sh
###
### WARNING: This feature is new and experimental. Use it at your own risk!
###
ROOT=/INSECURE/devtools/phabricator
exec "$ROOT/bin/ssh-auth" $@

View file

@ -0,0 +1,24 @@
###
### WARNING: This feature is new and experimental. Use it at your own risk!
###
# You must have OpenSSHD 6.2 or newer; support for AuthorizedKeysCommand was
# added in this version.
Port 2222
AuthorizedKeysCommand /etc/phabricator-ssh-hook.sh
AuthorizedKeysCommandUser some-unprivileged-user
# You may need to tweak these options, but mostly they just turn off everything
# dangerous.
Protocol 2
PermitRootLogin no
AllowAgentForwarding no
AllowTcpForwarding no
PrintMotd no
PrintLastLog no
PasswordAuthentication no
AuthorizedKeysFile none
PidFile /var/run/sshd-phabricator.pid

61
scripts/ssh/ssh-auth-key.php Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$cert = file_get_contents('php://stdin');
if (!$cert) {
exit(1);
}
$parts = preg_split('/\s+/', $cert);
if (count($parts) < 2) {
exit(1);
}
list($type, $body) = $parts;
$user_dao = new PhabricatorUser();
$ssh_dao = new PhabricatorUserSSHKey();
$conn_r = $user_dao->establishConnection('r');
$row = queryfx_one(
$conn_r,
'SELECT userName FROM %T u JOIN %T ssh ON u.phid = ssh.userPHID
WHERE ssh.keyType = %s AND ssh.keyBody = %s',
$user_dao->getTableName(),
$ssh_dao->getTableName(),
$type,
$body);
if (!$row) {
exit(1);
}
$user = idx($row, 'userName');
if (!$user) {
exit(1);
}
if (!PhabricatorUser::validateUsername($user)) {
exit(1);
}
$bin = $root.'/bin/ssh-exec';
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
// This is additional escaping for the SSH 'command="..."' string.
$cmd = addcslashes($cmd, '"\\');
$options = array(
'command="'.$cmd.'"',
'no-port-forwarding',
'no-X11-forwarding',
'no-agent-forwarding',
'no-pty',
);
echo implode(',', $options);
exit(0);

View file

@ -4,58 +4,45 @@
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$cert = file_get_contents('php://stdin');
if (!$cert) {
exit(1);
}
$parts = preg_split('/\s+/', $cert);
if (count($parts) < 2) {
exit(1);
}
list($type, $body) = $parts;
$user_dao = new PhabricatorUser();
$ssh_dao = new PhabricatorUserSSHKey();
$conn_r = $user_dao->establishConnection('r');
$row = queryfx_one(
$rows = queryfx_all(
$conn_r,
'SELECT userName FROM %T u JOIN %T ssh ON u.phid = ssh.userPHID
WHERE ssh.keyType = %s AND ssh.keyBody = %s',
'SELECT userName, keyBody, keyType FROM %T u JOIN %T ssh
ON u.phid = ssh.userPHID',
$user_dao->getTableName(),
$ssh_dao->getTableName(),
$type,
$body);
if (!$row) {
exit(1);
}
$user = idx($row, 'userName');
if (!$user) {
exit(1);
}
if (!PhabricatorUser::validateUsername($user)) {
exit(1);
}
$ssh_dao->getTableName());
$bin = $root.'/bin/ssh-exec';
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
// This is additional escaping for the SSH 'command="..."' string.
$cmd = str_replace('"', '\\"', $cmd);
foreach ($rows as $row) {
$user = $row['userName'];
$options = array(
'command="'.$cmd.'"',
'no-port-forwarding',
'no-X11-forwarding',
'no-agent-forwarding',
'no-pty',
);
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
// This is additional escaping for the SSH 'command="..."' string.
$cmd = addcslashes($cmd, '"\\');
echo implode(',', $options);
// Strip out newlines and other nonsense from the key type and key body.
$type = $row['keyType'];
$type = preg_replace('@[\x00-\x20]+@', '', $type);
$key = $row['keyBody'];
$key = preg_replace('@[\x00-\x20]+@', '', $key);
$options = array(
'command="'.$cmd.'"',
'no-port-forwarding',
'no-X11-forwarding',
'no-agent-forwarding',
'no-pty',
);
$options = implode(',', $options);
$lines[] = $options.' '.$type.' '.$key."\n";
}
echo implode('', $lines);
exit(0);

View file

@ -4,29 +4,25 @@
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$original_command = getenv('SSH_ORIGINAL_COMMAND');
$original_argv = id(new PhutilShellLexer())->splitArguments($original_command);
$argv = array_merge($argv, $original_argv);
// First, figure out the authenticated user.
$args = new PhutilArgumentParser($argv);
$args->setTagline('receive SSH requests');
$args->setSynopsis(<<<EOSYNOPSIS
**ssh-exec** --phabricator-ssh-user __user__ __commmand__ [__options__]
**ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__]
Receive SSH requests.
EOSYNOPSIS
);
// NOTE: Do NOT parse standard arguments. Arguments are coming from a remote
// client over SSH, and they should not be able to execute "--xprofile",
// "--recon", etc.
$args->parsePartial(
$args->parse(
array(
array(
'name' => 'phabricator-ssh-user',
'param' => 'username',
),
array(
'name' => 'ssh-command',
'param' => 'command',
),
));
try {
@ -46,24 +42,36 @@ try {
throw new Exception("You have been exiled.");
}
$workflows = array(
new ConduitSSHWorkflow(),
);
// This duplicates logic in parseWorkflows(), but allows us to raise more
// concise/relevant exceptions when the client is a remote SSH.
$remain = $args->getUnconsumedArgumentVector();
if (empty($remain)) {
throw new Exception("No interactive logins.");
if ($args->getArg('ssh-command')) {
$original_command = $args->getArg('ssh-command');
} else {
$command = head($remain);
$workflow_names = mpull($workflows, 'getName', 'getName');
if (empty($workflow_names[$command])) {
throw new Exception("Invalid command.");
}
$original_command = getenv('SSH_ORIGINAL_COMMAND');
}
$workflow = $args->parseWorkflows($workflows);
// Now, rebuild the original command.
$original_argv = id(new PhutilShellLexer())
->splitArguments($original_command);
if (!$original_argv) {
throw new Exception("No interactive logins.");
}
$command = head($original_argv);
array_unshift($original_argv, 'phabricator-ssh-exec');
$original_args = new PhutilArgumentParser($original_argv);
$workflows = array(
new ConduitSSHWorkflow(),
new DiffusionSSHGitUploadPackWorkflow(),
new DiffusionSSHGitReceivePackWorkflow(),
);
$workflow_names = mpull($workflows, 'getName', 'getName');
if (empty($workflow_names[$command])) {
throw new Exception("Invalid command.");
}
$workflow = $original_args->parseWorkflows($workflows);
$workflow->setUser($user);
$sock_stdin = fopen('php://stdin', 'r');
@ -76,16 +84,24 @@ try {
throw new Exception("Unable to open stdout.");
}
$sock_stderr = fopen('php://stderr', 'w');
if (!$sock_stderr) {
throw new Exception("Unable to open stderr.");
}
$socket_channel = new PhutilSocketChannel(
$sock_stdin,
$sock_stdout);
$error_channel = new PhutilSocketChannel(null, $sock_stderr);
$metrics_channel = new PhutilMetricsChannel($socket_channel);
$workflow->setIOChannel($metrics_channel);
$workflow->setErrorChannel($error_channel);
$err = $workflow->execute($args);
$err = $workflow->execute($original_args);
$metrics_channel->flush();
$error_channel->flush();
} catch (Exception $ex) {
echo "phabricator-ssh-exec: ".$ex->getMessage()."\n";
fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n");
exit(1);
}

View file

@ -449,6 +449,11 @@ phutil_register_library_map(array(
'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php',
'DiffusionBrowseSearchController' => 'applications/diffusion/controller/DiffusionBrowseSearchController.php',
'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php',
'DiffusionCapabilityCreateRepositories' => 'applications/diffusion/capability/DiffusionCapabilityCreateRepositories.php',
'DiffusionCapabilityDefaultEdit' => 'applications/diffusion/capability/DiffusionCapabilityDefaultEdit.php',
'DiffusionCapabilityDefaultPush' => 'applications/diffusion/capability/DiffusionCapabilityDefaultPush.php',
'DiffusionCapabilityDefaultView' => 'applications/diffusion/capability/DiffusionCapabilityDefaultView.php',
'DiffusionCapabilityPush' => 'applications/diffusion/capability/DiffusionCapabilityPush.php',
'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php',
'DiffusionCommentListView' => 'applications/diffusion/view/DiffusionCommentListView.php',
'DiffusionCommentView' => 'applications/diffusion/view/DiffusionCommentView.php',
@ -475,6 +480,7 @@ phutil_register_library_map(array(
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php',
'DiffusionGitStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionGitStableCommitNameQuery.php',
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
@ -504,6 +510,7 @@ phutil_register_library_map(array(
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php',
'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php',
'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php',
'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php',
'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php',
@ -511,6 +518,7 @@ phutil_register_library_map(array(
'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php',
'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php',
'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php',
'DiffusionRepositoryEditLocalController' => 'applications/diffusion/controller/DiffusionRepositoryEditLocalController.php',
'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php',
'DiffusionRepositoryEditPolicyController' => 'applications/diffusion/controller/DiffusionRepositoryEditPolicyController.php',
@ -519,6 +527,10 @@ phutil_register_library_map(array(
'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
'DiffusionSSHGitReceivePackWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php',
'DiffusionSSHGitUploadPackWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php',
'DiffusionSSHGitWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitWorkflow.php',
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
'DiffusionStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionStableCommitNameQuery.php',
'DiffusionSvnCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionSvnCommitParentsQuery.php',
@ -1628,10 +1640,8 @@ phutil_register_library_map(array(
'PhabricatorRepositoryCommitSearchIndexer' => 'applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php',
'PhabricatorRepositoryConfigOptions' => 'applications/repository/PhabricatorRepositoryConfigOptions.php',
'PhabricatorRepositoryController' => 'applications/repository/controller/PhabricatorRepositoryController.php',
'PhabricatorRepositoryCreateController' => 'applications/repository/controller/PhabricatorRepositoryCreateController.php',
'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php',
'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php',
'PhabricatorRepositoryEditController' => 'applications/repository/controller/PhabricatorRepositoryEditController.php',
'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php',
'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php',
'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php',
@ -1862,6 +1872,7 @@ phutil_register_library_map(array(
'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php',
'PhabricatorUserTitleField' => 'applications/people/customfield/PhabricatorUserTitleField.php',
'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php',
'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php',
'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php',
'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php',
'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
@ -2634,6 +2645,11 @@ phutil_register_library_map(array(
'DiffusionBrowseMainController' => 'DiffusionBrowseController',
'DiffusionBrowseSearchController' => 'DiffusionBrowseController',
'DiffusionBrowseTableView' => 'DiffusionView',
'DiffusionCapabilityCreateRepositories' => 'PhabricatorPolicyCapability',
'DiffusionCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
'DiffusionCapabilityDefaultPush' => 'PhabricatorPolicyCapability',
'DiffusionCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'DiffusionCapabilityPush' => 'PhabricatorPolicyCapability',
'DiffusionChangeController' => 'DiffusionController',
'DiffusionCommentListView' => 'AphrontView',
'DiffusionCommentView' => 'AphrontView',
@ -2658,6 +2674,7 @@ phutil_register_library_map(array(
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
'DiffusionGitRequest' => 'DiffusionRequest',
'DiffusionGitResponse' => 'AphrontResponse',
'DiffusionGitStableCommitNameQuery' => 'DiffusionStableCommitNameQuery',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryTableView' => 'DiffusionView',
@ -2681,6 +2698,7 @@ phutil_register_library_map(array(
'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'DiffusionRepositoryController' => 'DiffusionController',
'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryDefaultController' => 'DiffusionController',
'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController',
@ -2688,6 +2706,7 @@ phutil_register_library_map(array(
'DiffusionRepositoryEditController' => 'DiffusionController',
'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditLocalController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditPolicyController' => 'DiffusionRepositoryEditController',
@ -2697,6 +2716,10 @@ phutil_register_library_map(array(
0 => 'DiffusionController',
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
),
'DiffusionSSHGitReceivePackWorkflow' => 'DiffusionSSHGitWorkflow',
'DiffusionSSHGitUploadPackWorkflow' => 'DiffusionSSHGitWorkflow',
'DiffusionSSHGitWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
'DiffusionSetupException' => 'AphrontUsageException',
'DiffusionStableCommitNameQuery' => 'DiffusionQuery',
'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery',
@ -3955,10 +3978,8 @@ phutil_register_library_map(array(
'PhabricatorRepositoryCommitSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRepositoryController' => 'PhabricatorController',
'PhabricatorRepositoryCreateController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryEditController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
@ -4192,6 +4213,7 @@ phutil_register_library_map(array(
'PhabricatorUserTestCase' => 'PhabricatorTestCase',
'PhabricatorUserTitleField' => 'PhabricatorUserCustomField',
'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorVCSResponse' => 'AphrontResponse',
'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',

View file

@ -49,6 +49,10 @@ abstract class AphrontResponse {
return $this->responseCode;
}
public function getHTTPResponseMessage() {
return '';
}
public function setFrameable($frameable) {
$this->frameable = $frameable;
return $this;

View file

@ -25,13 +25,13 @@ abstract class AphrontHTTPSink {
* @param int Numeric HTTP status code.
* @return void
*/
final public function writeHTTPStatus($code) {
final public function writeHTTPStatus($code, $message = '') {
if (!preg_match('/^\d{3}$/', $code)) {
throw new Exception("Malformed HTTP status code '{$code}'!");
}
$code = (int)$code;
$this->emitHTTPStatus($code);
$this->emitHTTPStatus($code, $message);
}
@ -103,7 +103,9 @@ abstract class AphrontHTTPSink {
$response->getHeaders(),
$response->getCacheHeaders());
$this->writeHTTPStatus($response->getHTTPResponseCode());
$this->writeHTTPStatus(
$response->getHTTPResponseCode(),
$response->getHTTPResponseMessage());
$this->writeHeaders($all_headers);
$this->writeData($response_string);
}
@ -112,7 +114,7 @@ abstract class AphrontHTTPSink {
/* -( Emitting the Response )---------------------------------------------- */
abstract protected function emitHTTPStatus($code);
abstract protected function emitHTTPStatus($code, $message = '');
abstract protected function emitHeader($name, $value);
abstract protected function emitData($data);
}

View file

@ -11,7 +11,7 @@ final class AphrontIsolatedHTTPSink extends AphrontHTTPSink {
private $headers;
private $data;
protected function emitHTTPStatus($code) {
protected function emitHTTPStatus($code, $message = '') {
$this->status = $code;
}

View file

@ -7,9 +7,13 @@
*/
final class AphrontPHPHTTPSink extends AphrontHTTPSink {
protected function emitHTTPStatus($code) {
protected function emitHTTPStatus($code, $message = '') {
if ($code != 200) {
header("HTTP/1.0 {$code}");
$header = "HTTP/1.0 {$code}";
if (strlen($message)) {
$header .= " {$message}";
}
header($header);
}
}

View file

@ -24,7 +24,7 @@ abstract class PhabricatorController extends AphrontController {
return PhabricatorUserEmail::isEmailVerificationRequired();
}
final public function willBeginExecution() {
public function willBeginExecution() {
$request = $this->getRequest();
if ($request->getUser()) {

View file

@ -31,7 +31,7 @@ final class ConduitSSHWorkflow extends PhabricatorSSHWorkflow {
throw new Exception("Invalid JSON input.");
}
$params = idx($raw_params, 'params', array());
$params = idx($raw_params, 'params', '[]');
$params = json_decode($params, true);
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);

View file

@ -162,6 +162,8 @@ final class PhabricatorSetupCheckExtraConfig extends PhabricatorSetupCheck {
'been migrated.'),
'differential.custom-remarkup-rules' => $markup_reason,
'differential.custom-remarkup-block-rules' => $markup_reason,
'auth.sshkeys.enabled' => pht(
'SSH keys are now actually useful, so they are always enabled.'),
);
return $ancient_config;

View file

@ -29,21 +29,6 @@ final class PhabricatorAuthenticationConfigOptions
pht(
"Maximum number of simultaneous Conduit sessions each user is ".
"permitted to have.")),
$this->newOption('auth.sshkeys.enabled', 'bool', false)
->setBoolOptions(
array(
pht("Enable SSH key storage"),
pht("Disable SSH key storage")))
->setSummary(
pht("Allow users to associate SSH keys with their accounts."))
->setDescription(
pht(
"Set this true to enable the Settings -> SSH Public Keys panel, ".
"which will allow users to associated SSH public keys with their ".
"accounts. This is only really useful if you're setting up ".
"services over SSH and want to use Phabricator for ".
"authentication; in most situations you can leave this ".
"disabled.")),
$this->newOption('auth.require-email-verification', 'bool', false)
->setBoolOptions(
array(

View file

@ -75,8 +75,17 @@ final class PhabricatorApplicationDiffusion extends PhabricatorApplication {
'(?P<edit>remote)/' => 'DiffusionRepositoryCreateController',
'local/' => 'DiffusionRepositoryEditLocalController',
'delete/' => 'DiffusionRepositoryEditDeleteController',
'hosting/' => 'DiffusionRepositoryEditHostingController',
'(?P<serve>serve)/' => 'DiffusionRepositoryEditHostingController',
),
),
// NOTE: This must come after the rule above; it just gives us a
// catch-all for serving repositories over HTTP. We must accept
// requests without the trailing "/" because SVN commands don't
// necessarily include it.
'(?P<callsign>[A-Z]+)(/|$).*' => 'DiffusionRepositoryDefaultController',
'inline/' => array(
'edit/(?P<phid>[^/]+)/' => 'DiffusionInlineCommentController',
'preview/(?P<phid>[^/]+)/' =>
@ -103,4 +112,19 @@ final class PhabricatorApplicationDiffusion extends PhabricatorApplication {
return 0.120;
}
protected function getCustomCapabilities() {
return array(
DiffusionCapabilityDefaultView::CAPABILITY => array(
),
DiffusionCapabilityDefaultEdit::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
DiffusionCapabilityDefaultPush::CAPABILITY => array(
),
DiffusionCapabilityCreateRepositories::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
);
}
}

View file

@ -0,0 +1,20 @@
<?php
final class DiffusionCapabilityCreateRepositories
extends PhabricatorPolicyCapability {
const CAPABILITY = 'diffusion.create';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Can Create Repositories');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to create new repositories.');
}
}

View file

@ -0,0 +1,16 @@
<?php
final class DiffusionCapabilityDefaultEdit
extends PhabricatorPolicyCapability {
const CAPABILITY = 'diffusion.default.edit';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Default Edit Policy');
}
}

View file

@ -0,0 +1,16 @@
<?php
final class DiffusionCapabilityDefaultPush
extends PhabricatorPolicyCapability {
const CAPABILITY = 'diffusion.default.push';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Default Push Policy');
}
}

View file

@ -0,0 +1,20 @@
<?php
final class DiffusionCapabilityDefaultView
extends PhabricatorPolicyCapability {
const CAPABILITY = 'diffusion.default.view';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Default View Policy');
}
public function shouldAllowPublicPolicySetting() {
return true;
}
}

View file

@ -0,0 +1,20 @@
<?php
final class DiffusionCapabilityPush
extends PhabricatorPolicyCapability {
const CAPABILITY = 'diffusion.push';
public function getCapabilityKey() {
return self::CAPABILITY;
}
public function getCapabilityName() {
return pht('Can Push');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to push to this repository.');
}
}

View file

@ -76,7 +76,6 @@ final class DiffusionCommitController extends DiffusionController {
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
$changesets = null;
if ($is_foreign) {
@ -154,10 +153,14 @@ final class DiffusionCommitController extends DiffusionController {
$hard_limit = 1000;
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
if ($commit->isImported()) {
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
} else {
$changes = array();
}
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
@ -206,37 +209,29 @@ final class DiffusionCommitController extends DiffusionController {
}
if ($bad_commit) {
$error_panel = new AphrontErrorView();
$error_panel->setTitle(pht('Bad Commit'));
$error_panel->appendChild($bad_commit['description']);
$content[] = $error_panel;
$content[] = $this->renderStatusMessage(
pht('Bad Commit'),
$bad_commit['description']);
} else if ($is_foreign) {
// Don't render anything else.
} else if (!$commit->isImported()) {
$content[] = $this->renderStatusMessage(
pht('Still Importing...'),
pht(
'This commit is still importing. Changes will be visible once '.
'the import finishes.'));
} else if (!count($changes)) {
$no_changes = new AphrontErrorView();
$no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$no_changes->setTitle(pht('Not Yet Parsed'));
// TODO: This can also happen with weird SVN changes that don't do
// anything (or only alter properties?), although the real no-changes case
// is extremely rare and might be impossible to produce organically. We
// should probably write some kind of "Nothing Happened!" change into the
// DB once we parse these changes so we can distinguish between
// "not parsed yet" and "no changes".
$no_changes->appendChild(
pht("This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths)."));
$content[] = $no_changes;
$content[] = $this->renderStatusMessage(
pht('Empty Commit'),
pht(
'This commit is empty and does not affect any paths.'));
} else if ($was_limited) {
$huge_commit = new AphrontErrorView();
$huge_commit->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$huge_commit->setTitle(pht('Enormous Commit'));
$huge_commit->appendChild(
$content[] = $this->renderStatusMessage(
pht('Enormous Commit'),
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
$content[] = $huge_commit;
} else {
// The user has clicked "Show All Changes", and we should show all the
// changes inline even if there are more than the soft limit.
@ -253,6 +248,7 @@ final class DiffusionCommitController extends DiffusionController {
'href' => '?show_all=true',
),
pht('Show All Changes'));
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
@ -1084,4 +1080,5 @@ final class DiffusionCommitController extends DiffusionController {
return $parser->processCorpus($corpus);
}
}

View file

@ -4,6 +4,188 @@ abstract class DiffusionController extends PhabricatorController {
protected $diffusionRequest;
public function willBeginExecution() {
$request = $this->getRequest();
$uri = $request->getRequestURI();
// Check if this is a VCS request, e.g. from "git clone", "hg clone", or
// "svn checkout". If it is, we jump off into repository serving code to
// process the request.
$regex = '@^/diffusion/(?P<callsign>[A-Z]+)(/|$)@';
$matches = null;
if (preg_match($regex, (string)$uri, $matches)) {
$vcs = null;
$content_type = $request->getHTTPHeader('Content-Type');
if ($request->getExists('__vcs__')) {
// This is magic to make it easier for us to debug stuff by telling
// users to run:
//
// curl http://example.phabricator.com/diffusion/X/?__vcs__=1
//
// ...to get a human-readable error.
$vcs = $request->getExists('__vcs__');
} else if ($request->getExists('service')) {
$service = $request->getStr('service');
// We get this initially for `info/refs`.
// Git also gives us a User-Agent like "git/1.8.2.3".
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($content_type == 'application/x-git-upload-pack-request') {
// We get this for `git-upload-pack`.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($request->getExists('cmd')) {
// Mercurial also sends an Accept header like
// "application/mercurial-0.1", and a User-Agent like
// "mercurial/proto-1.0".
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
} else {
// Subversion also sends an initial OPTIONS request (vs GET/POST), and
// has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2)
// serf/1.3.2".
$dav = $request->getHTTPHeader('DAV');
$dav = new PhutilURI($dav);
if ($dav->getDomain() === 'subversion.tigris.org') {
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
}
}
if ($vcs) {
return $this->processVCSRequest($matches['callsign']);
}
}
parent::willBeginExecution();
}
private function processVCSRequest($callsign) {
// TODO: Authenticate user.
$viewer = new PhabricatorUser();
$allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
if (!$allow_public) {
if (!$viewer->isLoggedIn()) {
return new PhabricatorVCSResponse(
403,
pht('You must log in to access repositories.'));
}
}
try {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
return new PhabricatorVCSResponse(
404,
pht('No such repository exists.'));
}
} catch (PhabricatorPolicyException $ex) {
if ($viewer->isLoggedIn()) {
return new PhabricatorVCSResponse(
403,
pht('You do not have permission to access this repository.'));
} else {
return new PhabricatorVCSResponse(
401,
pht('You must log in to access this repository.'));
}
}
$is_push = !$this->isReadOnlyRequest($repository);
switch ($repository->getServeOverHTTP()) {
case PhabricatorRepository::SERVE_READONLY:
if ($is_push) {
return new PhabricatorVCSResponse(
403,
pht('This repository is read-only over HTTP.'));
}
break;
case PhabricatorRepository::SERVE_READWRITE:
if ($is_push) {
$can_push = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
DiffusionCapabilityPush::CAPABILITY);
if (!$can_push) {
if ($viewer->isLoggedIn()) {
return new PhabricatorVCSResponse(
403,
pht('You do not have permission to push to this repository.'));
} else {
return new PhabricatorVCSResponse(
401,
pht('You must log in to push to this repository.'));
}
}
}
break;
case PhabricatorRepository::SERVE_OFF:
default:
return new PhabricatorVCSResponse(
403,
pht('This repository is not available over HTTP.'));
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
return $this->serveGitRequest($repository);
default:
break;
}
return new PhabricatorVCSResponse(
999,
pht('TODO: Implement meaningful responses.'));
}
private function isReadOnlyRequest(
PhabricatorRepository $repository) {
$request = $this->getRequest();
$method = $_SERVER['REQUEST_METHOD'];
// TODO: This implementation is safe by default, but very incomplete.
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$service = $request->getStr('service');
$path = $this->getRequestDirectoryPath();
// NOTE: Service names are the reverse of what you might expect, as they
// are from the point of view of the server. The main read service is
// "git-upload-pack", and the main write service is "git-receive-pack".
if ($method == 'GET' &&
$path == '/info/refs' &&
$service == 'git-upload-pack') {
return true;
}
if ($path == '/git-upload-pack') {
return true;
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$cmd = $request->getStr('cmd');
switch ($cmd) {
case 'capabilities':
return true;
default:
return false;
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SUBVERSION:
break;
}
return false;
}
public function willProcessRequest(array $data) {
if (isset($data['callsign'])) {
$drequest = DiffusionRequest::newFromAphrontRequestDictionary(
@ -213,5 +395,58 @@ abstract class DiffusionController extends PhabricatorController {
return $links;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
private function serveGitRequest(PhabricatorRepository $repository) {
$request = $this->getRequest();
$request_path = $this->getRequestDirectoryPath();
$repository_root = $repository->getLocalPath();
// Rebuild the query string to strip `__magic__` parameters and prevent
// issues where we might interpret inputs like "service=read&service=write"
// differently than the server does and pass it an unsafe command.
$query_data = $request->getPassthroughRequestParameters();
$query_string = http_build_query($query_data, '', '&');
// We're about to wipe out PATH with the rest of the environment, so
// resolve the binary first.
$bin = Filesystem::resolveBinary('git-http-backend');
if (!$bin) {
throw new Exception("Unable to find `git-http-backend` in PATH!");
}
$env = array(
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
'QUERY_STRING' => $query_string,
'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
'REMOTE_USER' => '',
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'GIT_PROJECT_ROOT' => $repository_root,
'GIT_HTTP_EXPORT_ALL' => '1',
'PATH_INFO' => $request_path,
);
list($stdout) = id(new ExecFuture('%s', $bin))
->setEnv($env, true)
->write(PhabricatorStartup::getRawInput())
->resolvex();
return id(new DiffusionGitResponse())->setGitData($stdout);
}
private function getRequestDirectoryPath() {
$request = $this->getRequest();
$request_path = $request->getRequestURI()->getPath();
return preg_replace('@^/diffusion/[A-Z]+@', '', $request_path);
}
protected function renderStatusMessage($title, $body) {
return id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle($title)
->appendChild($body);
}
}

View file

@ -7,7 +7,8 @@ final class DiffusionRepositoryController extends DiffusionController {
}
public function processRequest() {
$drequest = $this->diffusionRequest;
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$content = array();
@ -15,106 +16,110 @@ final class DiffusionRepositoryController extends DiffusionController {
$content[] = $crumbs;
$content[] = $this->buildPropertiesTable($drequest->getRepository());
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => 0,
'limit' => 15));
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
$browse_results = DiffusionBrowseResultSet::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.browsequery',
array(
'path' => $drequest->getPath(),
'commit' => $drequest->getCommit(),
)));
$browse_paths = $browse_results->getPaths();
$phids = array();
foreach ($history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
try {
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => 0,
'limit' => 15));
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
foreach ($history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
$history_exception = null;
} catch (Exception $ex) {
$history_results = null;
$history = null;
$history_exception = $ex;
}
foreach ($browse_paths as $item) {
$data = $item->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
try {
$browse_results = DiffusionBrowseResultSet::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.browsequery',
array(
'path' => $drequest->getPath(),
'commit' => $drequest->getCommit(),
)));
$browse_paths = $browse_results->getPaths();
foreach ($browse_paths as $item) {
$data = $item->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
$browse_exception = null;
} catch (Exception $ex) {
$browse_results = null;
$browse_paths = null;
$browse_exception = $ex;
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$readme = $this->callConduitWithDiffusionRequest(
'diffusion.readmequery',
array(
'paths' => $browse_results->getPathDicts()
if ($browse_results) {
$readme = $this->callConduitWithDiffusionRequest(
'diffusion.readmequery',
array(
'paths' => $browse_results->getPathDicts()
));
} else {
$readme = null;
}
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHandles($handles);
$history_table->setHistory($history);
$history_table->loadRevisions();
$history_table->setParents($history_results['parents']);
$history_table->setIsHead(true);
$content[] = $this->buildHistoryTable(
$history_results,
$history,
$history_exception,
$handles);
$callsign = $drequest->getRepository()->getCallsign();
$all = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'history',
)),
),
pht('View Full Commit History'));
$content[] = $this->buildBrowseTable(
$browse_results,
$browse_paths,
$browse_exception,
$handles);
$panel = new AphrontPanelView();
$panel->setHeader(pht("Recent Commits &middot; %s", $all));
$panel->appendChild($history_table);
$panel->setNoBackground();
try {
$content[] = $this->buildTagListTable($drequest);
} catch (Exception $ex) {
if (!$repository->isImporting()) {
$content[] = $this->renderStatusMessage(
pht('Unable to Load Tags'),
$ex->getMessage());
}
}
$content[] = $panel;
$browse_table = new DiffusionBrowseTableView();
$browse_table->setDiffusionRequest($drequest);
$browse_table->setHandles($handles);
$browse_table->setPaths($browse_paths);
$browse_table->setUser($this->getRequest()->getUser());
$browse_panel = new AphrontPanelView();
$browse_panel->setHeader(phutil_tag(
'a',
array('href' => $drequest->generateURI(array('action' => 'browse'))),
pht('Browse Repository')));
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
$content[] = $browse_panel;
$content[] = $this->buildTagListTable($drequest);
$content[] = $this->buildBranchListTable($drequest);
try {
$content[] = $this->buildBranchListTable($drequest);
} catch (Exception $ex) {
if (!$repository->isImporting()) {
$content[] = $this->renderStatusMessage(
pht('Unable to Load Branches'),
$ex->getMessage());
}
}
if ($readme) {
$box = new PHUIBoxView();
@ -145,6 +150,15 @@ final class DiffusionRepositoryController extends DiffusionController {
->setUser($user)
->setPolicyObject($repository);
if (!$repository->isTracked()) {
$header->setStatus('policy-noone', '', pht('Inactive'));
} else if ($repository->isImporting()) {
$header->setStatus('time', 'red', pht('Importing...'));
} else {
$header->setStatus('oh-ok', '', pht('Active'));
}
$actions = $this->buildActionList($repository);
$view = id(new PHUIPropertyListView())
@ -333,4 +347,109 @@ final class DiffusionRepositoryController extends DiffusionController {
return $view;
}
private function buildHistoryTable(
$history_results,
$history,
$history_exception,
array $handles) {
$request = $this->getRequest();
$viewer = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if ($history_exception) {
if ($repository->isImporting()) {
return $this->renderStatusMessage(
pht('Still Importing...'),
pht(
'This repository is still importing. History is not yet '.
'available.'));
} else {
return $this->renderStatusMessage(
pht('Unable to Retrieve History'),
$history_exception->getMessage());
}
}
$history_table = id(new DiffusionHistoryTableView())
->setUser($viewer)
->setDiffusionRequest($drequest)
->setHandles($handles)
->setHistory($history);
// TODO: Super sketchy.
$history_table->loadRevisions();
if ($history_results) {
$history_table->setParents($history_results['parents']);
}
$history_table->setIsHead(true);
$callsign = $drequest->getRepository()->getCallsign();
$all = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'history',
)),
),
pht('View Full Commit History'));
$panel = new AphrontPanelView();
$panel->setHeader(pht("Recent Commits &middot; %s", $all));
$panel->appendChild($history_table);
$panel->setNoBackground();
return $panel;
}
private function buildBrowseTable(
$browse_results,
$browse_paths,
$browse_exception,
array $handles) {
$request = $this->getRequest();
$viewer = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if ($browse_exception) {
if ($repository->isImporting()) {
// The history table renders a useful message.
return null;
} else {
return $this->renderStatusMessage(
pht('Unable to Retrieve Paths'),
$browse_exception->getMessage());
}
}
$browse_table = id(new DiffusionBrowseTableView())
->setUser($viewer)
->setDiffusionRequest($drequest)
->setHandles($handles);
if ($browse_paths) {
$browse_table->setPaths($browse_paths);
} else {
$browse_table->setPaths(array());
}
$browse_uri = $drequest->generateURI(array('action' => 'browse'));
$browse_panel = new AphrontPanelView();
$browse_panel->setHeader(
phutil_tag(
'a',
array('href' => $browse_uri),
pht('Browse Repository')));
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
return $browse_panel;
}
}

View file

@ -32,6 +32,9 @@ final class DiffusionRepositoryCreateController
$cancel_uri = $this->getRepositoryControllerURI($repository, 'edit/');
} else {
$this->requireApplicationCapability(
DiffusionCapabilityCreateRepositories::CAPABILITY);
$cancel_uri = $this->getApplicationURI();
}
@ -60,15 +63,19 @@ final class DiffusionRepositoryCreateController
if ($request->isFormPost()) {
$form->readFromRequest($request);
if ($form->isComplete()) {
$is_create = ($this->edit === null);
if ($this->edit != 'remote') {
// TODO: This exception is heartwarming but should probably take more
// substantive actions.
throw new Exception("GOOD JOB AT FORM");
if ($is_create) {
$repository = PhabricatorRepository::initializeNewRepository(
$viewer);
}
$template = id(new PhabricatorRepositoryTransaction());
$type_name = PhabricatorRepositoryTransaction::TYPE_NAME;
$type_vcs = PhabricatorRepositoryTransaction::TYPE_VCS;
$type_activate = PhabricatorRepositoryTransaction::TYPE_ACTIVATE;
$type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH;
$type_remote_uri = PhabricatorRepositoryTransaction::TYPE_REMOTE_URI;
$type_ssh_login = PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN;
$type_ssh_key = PhabricatorRepositoryTransaction::TYPE_SSH_KEY;
@ -78,6 +85,48 @@ final class DiffusionRepositoryCreateController
$xactions = array();
// If we're creating a new repository, set all this core stuff.
if ($is_create) {
$callsign = $form->getPage('name')
->getControl('callsign')->getValue();
// We must set this to a unique value to save the repository
// initially, and it's immutable, so we don't bother using
// transactions to apply this change.
$repository->setCallsign($callsign);
// Put the repository in "Importing" mode until we finish
// parsing it.
$repository->setDetail('importing', true);
$xactions[] = id(clone $template)
->setTransactionType($type_name)
->setNewValue(
$form->getPage('name')->getControl('name')->getValue());
$xactions[] = id(clone $template)
->setTransactionType($type_vcs)
->setNewValue(
$form->getPage('vcs')->getControl('vcs')->getValue());
$activate = $form->getPage('done')
->getControl('activate')->getValue();
$xactions[] = id(clone $template)
->setTransactionType($type_activate)
->setNewValue(
($activate == 'start'));
$default_local_path = PhabricatorEnv::getEnvConfig(
'repository.default-local-path');
$default_local_path = rtrim($default_local_path, '/');
$default_local_path = $default_local_path.'/'.$callsign.'/';
$xactions[] = id(clone $template)
->setTransactionType($type_local_path)
->setNewValue($default_local_path);
}
$xactions[] = id(clone $template)
->setTransactionType($type_remote_uri)
->setNewValue(
@ -408,6 +457,21 @@ final class DiffusionRepositoryCreateController
}
}
// Catch confusion between Git/SCP-style URIs and normal URIs. See T3619
// for discussion. This is usually a user adding "ssh://" to an implicit
// SSH Git URI.
if ($proto == 'ssh') {
if (preg_match('(^[^:@]+://[^/:]+:[^\d])', $v_remote)) {
$c_remote->setError(pht('Invalid'));
$page->addPageError(
pht(
"The Remote URI is not formatted correctly. Remote URIs ".
"with an explicit protocol should be in the form ".
"'proto://domain/path', not 'proto://domain:/path'. ".
"The ':/path' syntax is only valid in SCP-style URIs."));
}
}
switch ($proto) {
case 'ssh':
case 'http':
@ -619,7 +683,7 @@ final class DiffusionRepositoryCreateController
pht('Configure More Options First'),
pht(
'Configure more options before beginning the repository '.
'import. This will let you fine-tune settings.. You can '.
'import. This will let you fine-tune settings. You can '.
'start the import whenever you are ready.')));
}

View file

@ -0,0 +1,11 @@
<?php
final class DiffusionRepositoryDefaultController extends DiffusionController {
public function processRequest() {
// NOTE: This controller is just here to make sure we call
// willBeginExecution() on any /diffusion/X/ URI, so we can intercept
// `git`, `hg` and `svn` HTTP protocol requests.
return new Aphront404Response();
}
}

View file

@ -37,8 +37,12 @@ final class DiffusionRepositoryEditBranchesController
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$v_default = $repository->getHumanReadableDetail('default-branch');
$v_track = $repository->getHumanReadableDetail('branch-filter');
$v_autoclose = $repository->getHumanReadableDetail('close-commits-filter');
$v_track = $repository->getHumanReadableDetail(
'branch-filter',
array());
$v_autoclose = $repository->getHumanReadableDetail(
'close-commits-filter',
array());
if ($request->isFormPost()) {
$v_default = $request->getStr('default');

View file

@ -0,0 +1,253 @@
<?php
final class DiffusionRepositoryEditHostingController
extends DiffusionRepositoryEditController {
private $serve;
public function willProcessRequest(array $data) {
parent::willProcessRequest($data);
$this->serve = idx($data, 'serve');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($repository->getID()))
->executeOne();
if (!$repository) {
return new Aphront404Response();
}
if (!$this->serve) {
return $this->handleHosting($repository);
} else {
return $this->handleProtocols($repository);
}
}
public function handleHosting(PhabricatorRepository $repository) {
$request = $this->getRequest();
$user = $request->getUser();
$v_hosting = $repository->isHosted();
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$next_uri = $this->getRepositoryControllerURI($repository, 'edit/serve/');
if ($request->isFormPost()) {
$v_hosting = $request->getBool('hosting');
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_hosting = PhabricatorRepositoryTransaction::TYPE_HOSTING;
$xactions[] = id(clone $template)
->setTransactionType($type_hosting)
->setNewValue($v_hosting);
id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($user)
->applyTransactions($repository, $xactions);
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Hosting')));
$title = pht('Edit Hosting (%s)', $repository->getName());
$hosted_control = id(new AphrontFormRadioButtonControl())
->setName('hosting')
->setLabel(pht('Hosting'))
->addButton(
true,
pht('Host Repository on Phabricator'),
pht(
'Phabricator will host this repository. Users will be able to '.
'push commits to Phabricator. Phabricator will not pull '.
'changes from elsewhere.'))
->addButton(
false,
pht('Host Repository Elsewhere'),
pht(
'Phabricator will pull updates to this repository from a master '.
'repository elsewhere (for example, on GitHub or Bitbucket). '.
'Users will not be able to push commits to this repository.'))
->setValue($v_hosting);
$form = id(new AphrontFormView())
->setUser($user)
->appendRemarkupInstructions(
pht(
'NOTE: Hosting is extremely new and barely works! Use it at '.
'your own risk.'.
"\n\n".
'Phabricator can host repositories, or it can track repositories '.
'hosted elsewhere (like on GitHub or Bitbucket).'))
->appendChild($hosted_control)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save and Continue'))
->addCancelButton($edit_uri));
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
),
array(
'title' => $title,
'device' => true,
));
}
public function handleProtocols(PhabricatorRepository $repository) {
$request = $this->getRequest();
$user = $request->getUser();
$v_http_mode = $repository->getServeOverHTTP();
$v_ssh_mode = $repository->getServeOverSSH();
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$prev_uri = $this->getRepositoryControllerURI($repository, 'edit/hosting/');
if ($request->isFormPost()) {
$v_http_mode = $request->getStr('http');
$v_ssh_mode = $request->getStr('ssh');
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_http = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
$type_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
$xactions[] = id(clone $template)
->setTransactionType($type_http)
->setNewValue($v_http_mode);
$xactions[] = id(clone $template)
->setTransactionType($type_ssh)
->setNewValue($v_ssh_mode);
id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($user)
->applyTransactions($repository, $xactions);
return id(new AphrontRedirectResponse())->setURI($edit_uri);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Protocols')));
$title = pht('Edit Protocols (%s)', $repository->getName());
if ($repository->isHosted()) {
$rw_message = pht(
'Phabricator will serve a read-write copy of this repository');
} else {
$rw_message = pht(
'This repository is hosted elsewhere, so Phabricator can not perform '.
'writes.');
}
$ssh_control =
id(new AphrontFormRadioButtonControl())
->setName('ssh')
->setLabel(pht('SSH'))
->setValue($v_ssh_mode)
->addButton(
PhabricatorRepository::SERVE_OFF,
PhabricatorRepository::getProtocolAvailabilityName(
PhabricatorRepository::SERVE_OFF),
pht('Phabricator will not serve this repository.'))
->addButton(
PhabricatorRepository::SERVE_READONLY,
PhabricatorRepository::getProtocolAvailabilityName(
PhabricatorRepository::SERVE_READONLY),
pht('Phabricator will serve a read-only copy of this repository.'))
->addButton(
PhabricatorRepository::SERVE_READWRITE,
PhabricatorRepository::getProtocolAvailabilityName(
PhabricatorRepository::SERVE_READWRITE),
$rw_message,
$repository->isHosted() ? null : 'disabled',
$repository->isHosted() ? null : true);
$http_control =
id(new AphrontFormRadioButtonControl())
->setName('http')
->setLabel(pht('HTTP'))
->setValue($v_http_mode)
->addButton(
PhabricatorRepository::SERVE_OFF,
PhabricatorRepository::getProtocolAvailabilityName(
PhabricatorRepository::SERVE_OFF),
pht('Phabricator will not serve this repository.'))
->addButton(
PhabricatorRepository::SERVE_READONLY,
PhabricatorRepository::getProtocolAvailabilityName(
PhabricatorRepository::SERVE_READONLY),
pht('Phabricator will serve a read-only copy of this repository.'))
->addButton(
PhabricatorRepository::SERVE_READWRITE,
PhabricatorRepository::getProtocolAvailabilityName(
PhabricatorRepository::SERVE_READWRITE),
$rw_message,
$repository->isHosted() ? null : 'disabled',
$repository->isHosted() ? null : true);
$form = id(new AphrontFormView())
->setUser($user)
->appendRemarkupInstructions(
pht(
'Phabricator can serve repositories over various protocols. You can '.
'configure server protocols here.'))
->appendChild($ssh_control)
->appendChild($http_control)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Changes'))
->addCancelButton($prev_uri, pht('Back')));
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
),
array(
'title' => $title,
'device' => true,
));
}
}

View file

@ -23,8 +23,7 @@ final class DiffusionRepositoryEditMainController
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// TOOD: This will be true for hosted SVN repositories.
$has_local = false;
$has_local = $repository->isHosted();
$is_svn = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
@ -63,6 +62,10 @@ final class DiffusionRepositoryEditMainController
$encoding_properties =
$this->buildEncodingProperties($repository, $encoding_actions);
$hosting_properties = $this->buildHostingProperties(
$repository,
$this->buildHostingActions($repository));
$branches_properties = null;
if ($has_branches) {
$branches_properties = $this->buildBranchesProperties(
@ -114,6 +117,7 @@ final class DiffusionRepositoryEditMainController
->setHeader($header)
->addPropertyList($basic_properties)
->addPropertyList($policy_properties)
->addPropertyList($hosting_properties)
->addPropertyList($remote_properties);
if ($local_properties) {
@ -298,6 +302,10 @@ final class DiffusionRepositoryEditMainController
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$pushable = $repository->isHosted()
? $descriptions[DiffusionCapabilityPush::CAPABILITY]
: phutil_tag('em', array(), pht('Not a Hosted Repository'));
$view->addProperty(pht('Pushable By'), $pushable);
return $view;
}
@ -336,12 +344,12 @@ final class DiffusionRepositoryEditMainController
$view->addProperty(pht('Default Branch'), $default_branch);
$track_only = nonempty(
$repository->getHumanReadableDetail('branch-filter'),
$repository->getHumanReadableDetail('branch-filter', array()),
phutil_tag('em', array(), pht('Track All Branches')));
$view->addProperty(pht('Track Only'), $track_only);
$autoclose_only = nonempty(
$repository->getHumanReadableDetail('close-commits-filter'),
$repository->getHumanReadableDetail('close-commits-filter', array()),
phutil_tag('em', array(), pht('Autoclose On All Branches')));
$view->addProperty(pht('Autoclose Only'), $autoclose_only);
@ -501,4 +509,57 @@ final class DiffusionRepositoryEditMainController
return $view;
}
private function buildHostingActions(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$edit = id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Hosting'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/hosting/'));
$view->addAction($edit);
return $view;
}
private function buildHostingProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$user = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($user)
->setActionList($actions)
->addSectionHeader(pht('Hosting'));
$hosting = $repository->isHosted()
? pht('Hosted on Phabricator')
: pht('Hosted Elsewhere');
$view->addProperty(pht('Hosting'), phutil_tag('em', array(), $hosting));
$view->addProperty(
pht('Serve over HTTP'),
phutil_tag(
'em',
array(),
PhabricatorRepository::getProtocolAvailabilityName(
$repository->getServeOverHTTP())));
$view->addProperty(
pht('Serve over SSH'),
phutil_tag(
'em',
array(),
PhabricatorRepository::getProtocolAvailabilityName(
$repository->getServeOverSSH())));
return $view;
}
}

View file

@ -27,16 +27,19 @@ final class DiffusionRepositoryEditPolicyController
$v_view = $repository->getViewPolicy();
$v_edit = $repository->getEditPolicy();
$v_push = $repository->getPushPolicy();
if ($request->isFormPost()) {
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$v_push = $request->getStr('pushPolicy');
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$type_push = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY;
$xactions[] = id(clone $template)
->setTransactionType($type_view)
@ -46,6 +49,12 @@ final class DiffusionRepositoryEditPolicyController
->setTransactionType($type_edit)
->setNewValue($v_edit);
if ($repository->isHosted()) {
$xactions[] = id(clone $template)
->setTransactionType($type_push)
->setNewValue($v_push);
}
id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
@ -62,7 +71,7 @@ final class DiffusionRepositoryEditPolicyController
id(new PhabricatorCrumbView())
->setName(pht('Edit Policies')));
$title = pht('Edit %s', $repository->getName());
$title = pht('Edit Policies (%s)', $repository->getName());
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
@ -84,7 +93,25 @@ final class DiffusionRepositoryEditPolicyController
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($repository)
->setPolicies($policies)
->setName('editPolicy'))
->setName('editPolicy'));
if ($repository->isHosted()) {
$form->appendChild(
id(new AphrontFormPolicyControl())
->setUser($viewer)
->setCapability(DiffusionCapabilityPush::CAPABILITY)
->setPolicyObject($repository)
->setPolicies($policies)
->setName('pushPolicy'));
} else {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Can Push'))
->setValue(
phutil_tag('em', array(), pht('Not a Hosted Repository'))));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Policies'))

View file

@ -90,7 +90,6 @@ final class DiffusionRepositoryListController extends DiffusionController
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new PhabricatorRepositorySearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
@ -100,6 +99,22 @@ final class DiffusionRepositoryListController extends DiffusionController
return $nav;
}
public function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
DiffusionCapabilityCreateRepositories::CAPABILITY);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Import Repository'))
->setHref($this->getApplicationURI('/create/'))
->setDisabled(!$can_create)
->setIcon('create'));
return $crumbs;
}
private function buildShortcuts() {
$shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll();
if ($shortcuts) {

View file

@ -7,8 +7,7 @@ extends DiffusionStableCommitNameQuery {
$repository = $this->getRepository();
$branch = $this->getBranch();
list($stdout) = $repository->execxLocalCommand(
'rev-parse --verify %s/%s',
DiffusionBranchInformation::DEFAULT_GIT_REMOTE,
'rev-parse --verify %s',
$branch);
$commit = trim($stdout);

View file

@ -31,8 +31,7 @@ final class DiffusionGitRequest extends DiffusionRequest {
if ($this->commit) {
return $this->commit;
}
$remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE;
return $remote.'/'.$this->getBranch();
return $this->getBranch();
}
}

View file

@ -0,0 +1,44 @@
<?php
final class DiffusionGitResponse extends AphrontResponse {
private $httpCode;
private $headers = array();
private $response;
public function setGitData($data) {
list($headers, $body) = explode("\r\n\r\n", $data, 2);
$this->response = $body;
$headers = explode("\r\n", $headers);
$matches = null;
$this->httpCode = 200;
$this->headers = array();
foreach ($headers as $header) {
if (preg_match('/^Status:\s*(\d+)/i', $header, $matches)) {
$this->httpCode = (int)$matches[1];
} else {
$this->headers[] = explode(': ', $header, 2);
}
}
return $this;
}
public function buildResponseString() {
return $this->response;
}
public function getHeaders() {
return $this->headers;
}
public function getCacheHeaders() {
return array();
}
public function getHTTPResponseCode() {
return $this->httpCode;
}
}

View file

@ -0,0 +1,34 @@
<?php
final class DiffusionSSHGitReceivePackWorkflow
extends DiffusionSSHGitWorkflow {
public function didConstruct() {
$this->setName('git-receive-pack');
$this->setArguments(
array(
array(
'name' => 'dir',
'wildcard' => true,
),
));
}
public function isReadOnly() {
return false;
}
public function getRequestPath() {
$args = $this->getArgs();
return head($args->getArg('dir'));
}
protected function executeRepositoryOperations(
PhabricatorRepository $repository) {
$future = new ExecFuture(
'git-receive-pack %s',
$repository->getLocalPath());
return $this->passthruIO($future);
}
}

View file

@ -0,0 +1,34 @@
<?php
final class DiffusionSSHGitUploadPackWorkflow
extends DiffusionSSHGitWorkflow {
public function didConstruct() {
$this->setName('git-upload-pack');
$this->setArguments(
array(
array(
'name' => 'dir',
'wildcard' => true,
),
));
}
public function isReadOnly() {
return true;
}
public function getRequestPath() {
$args = $this->getArgs();
return head($args->getArg('dir'));
}
protected function executeRepositoryOperations(
PhabricatorRepository $repository) {
$future = new ExecFuture('git-upload-pack %s', $repository->getLocalPath());
return $this->passthruIO($future);
}
}

View file

@ -0,0 +1,10 @@
<?php
abstract class DiffusionSSHGitWorkflow extends DiffusionSSHWorkflow {
protected function writeError($message) {
// Git assumes we'll add our own newlines.
return parent::writeError($message."\n");
}
}

View file

@ -0,0 +1,89 @@
<?php
abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
private $args;
public function getArgs() {
return $this->args;
}
abstract protected function isReadOnly();
abstract protected function getRequestPath();
abstract protected function executeRepositoryOperations(
PhabricatorRepository $repository);
protected function writeError($message) {
$this->getErrorChannel()->write($message);
return $this;
}
final public function execute(PhutilArgumentParser $args) {
$this->args = $args;
try {
$repository = $this->loadRepository();
return $this->executeRepositoryOperations($repository);
} catch (Exception $ex) {
$this->writeError(get_class($ex).': '.$ex->getMessage());
return 1;
}
}
private function loadRepository() {
$viewer = $this->getUser();
$path = $this->getRequestPath();
$regex = '@^/?diffusion/(?P<callsign>[A-Z]+)(?:/|$)@';
$matches = null;
if (!preg_match($regex, $path, $matches)) {
throw new Exception(
pht(
'Unrecognized repository path "%s". Expected a path like '.
'"%s".',
$path,
"/diffusion/X/"));
}
$callsign = $matches[1];
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
throw new Exception(
pht('No repository "%s" exists!', $callsign));
}
$is_push = !$this->isReadOnly();
switch ($repository->getServeOverSSH()) {
case PhabricatorRepository::SERVE_READONLY:
if ($is_push) {
throw new Exception(
pht('This repository is read-only over SSH.'));
}
break;
case PhabricatorRepository::SERVE_READWRITE:
if ($is_push) {
$can_push = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
DiffusionCapabilityPush::CAPABILITY);
if (!$can_push) {
throw new Exception(
pht('You do not have permission to push to this repository.'));
}
}
break;
case PhabricatorRepository::SERVE_OFF:
default:
throw new Exception(
pht('This repository is not available over SSH.'));
}
return $repository;
}
}

View file

@ -21,6 +21,8 @@ final class DiffusionHistoryTableView extends DiffusionView {
$commit_phids[] = $item->getCommit()->getPHID();
}
}
// TODO: Get rid of this.
$this->revisions = id(new DifferentialRevision())
->loadIDsByCommitPHIDs($commit_phids);
return $this;

View file

@ -16,14 +16,14 @@ final class PhabricatorRepositoryConfigOptions
public function getOptions() {
return array(
$this->newOption('repository.default-local-path', 'string', null)
$this->newOption('repository.default-local-path', 'string', '/var/repo/')
->setSummary(
pht("Default location to store local copies of repositories."))
->setDescription(
pht(
"The default location in which to store local copies of ".
"repositories. Anything stored in this directory will be assumed ".
"to be under the control of phabricator, which means that ".
"to be under the control of Phabricator, which means that ".
"Phabricator will try to do some maintenance on working copies ".
"if there are problems (such as a change to the remote origin ".
"url). This maintenance may include completely removing (and ".

View file

@ -29,10 +29,6 @@ final class PhabricatorApplicationRepositories extends PhabricatorApplication {
return array(
'/repository/' => array(
'' => 'PhabricatorRepositoryListController',
'create/' => 'PhabricatorRepositoryCreateController',
'edit/(?P<id>[1-9]\d*)/(?:(?P<view>\w+)/)?' =>
'PhabricatorRepositoryEditController',
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorRepositoryDeleteController',
'project/edit/(?P<id>[1-9]\d*)/' =>
'PhabricatorRepositoryArcanistProjectEditController',
'project/delete/(?P<id>[1-9]\d*)/' =>

View file

@ -62,15 +62,24 @@ final class ConduitAPI_repository_create_Method
}
protected function execute(ConduitAPIRequest $request) {
if (!$request->getUser()->getIsAdmin()) {
throw new ConduitException('ERR-PERMISSIONS');
}
$application = id(new PhabricatorApplicationQuery())
->setViewer($request->getUser())
->withClasses(array('PhabricatorApplicationDiffusion'))
->executeOne();
PhabricatorPolicyFilter::requireCapability(
$request->getUser(),
$application,
DiffusionCapabilityCreateRepositories::CAPABILITY);
// TODO: This has some duplication with (and lacks some of the validation
// of) the web workflow; refactor things so they can share more code as this
// stabilizes.
// stabilizes. Specifically, this should move to transactions since they
// work properly now.
$repository = PhabricatorRepository::initializeNewRepository(
$request->getUser());
$repository = new PhabricatorRepository();
$repository->setName($request->getValue('name'));
$callsign = $request->getValue('callsign');

View file

@ -1,119 +0,0 @@
<?php
final class PhabricatorRepositoryCreateController
extends PhabricatorRepositoryController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_name = true;
$e_callsign = true;
$repository = new PhabricatorRepository();
$type_map = PhabricatorRepositoryType::getAllRepositoryTypes();
$errors = array();
if ($request->isFormPost()) {
$repository->setName($request->getStr('name'));
$repository->setCallsign($request->getStr('callsign'));
$repository->setVersionControlSystem($request->getStr('type'));
if (!strlen($repository->getName())) {
$e_name = 'Required';
$errors[] = 'Repository name is required.';
} else {
$e_name = null;
}
if (!strlen($repository->getCallsign())) {
$e_callsign = 'Required';
$errors[] = 'Callsign is required.';
} else if (!preg_match('/^[A-Z]+$/', $repository->getCallsign())) {
$e_callsign = 'Invalid';
$errors[] = 'Callsign must be ALL UPPERCASE LETTERS.';
} else {
$e_callsign = null;
}
if (empty($type_map[$repository->getVersionControlSystem()])) {
$errors[] = 'Invalid version control system.';
}
if (!$errors) {
try {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository->getID().'/');
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_callsign = 'Duplicate';
$errors[] = 'Callsign must be unique. Another repository already '.
'uses that callsign.';
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
}
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/create/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($repository->getName())
->setError($e_name)
->setCaption('Human-readable repository name.'))
->appendChild(hsprintf(
'<p class="aphront-form-instructions">Select a "Callsign" &mdash; a '.
'short, uppercase string to identify revisions in this repository. If '.
'you choose "EX", revisions in this repository will be identified '.
'with the prefix "rEX".</p>'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Callsign')
->setName('callsign')
->setValue($repository->getCallsign())
->setError($e_callsign)
->setCaption(
'Short, UPPERCASE identifier. Once set, it can not be changed.'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Type')
->setName('type')
->setOptions($type_map)
->setValue($repository->getVersionControlSystem()))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Create Repository')
->addCancelButton('/repository/'));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Create Repository'))
->setFormError($error_view)
->setForm($form);
return $this->buildApplicationPage(
array(
$form_box,
),
array(
'title' => pht('Create Repository'),
'device' => true,
));
}
}

View file

@ -1,696 +0,0 @@
<?php
final class PhabricatorRepositoryEditController
extends PhabricatorRepositoryController {
private $id;
private $view;
private $repository;
private $sideNav;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$repository) {
return new Aphront404Response();
}
$views = array(
'basic' => 'Basics',
'tracking' => 'Tracking',
);
$this->repository = $repository;
if (!isset($views[$this->view])) {
$this->view = head_key($views);
}
$nav = new AphrontSideNavFilterView();
$base_uri = new PhutilURI('/repository/edit/'.$repository->getID().'/');
$nav->setBaseURI($base_uri);
foreach ($views as $view => $name) {
$nav->addFilter($view, $name);
}
$nav->selectFilter($this->view, null);
$nav->appendChild($this->renderDaemonNotice());
$this->sideNav = $nav;
switch ($this->view) {
case 'basic':
return $this->processBasicRequest();
case 'tracking':
return $this->processTrackingRequest();
default:
throw new Exception("Unknown view.");
}
}
protected function processBasicRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$errors = array();
$e_name = true;
if ($request->isFormPost()) {
$repository->setName($request->getStr('name'));
if (!strlen($repository->getName())) {
$e_name = 'Required';
$errors[] = 'Repository name is required.';
} else {
$e_name = null;
}
$repository->setDetail('description', $request->getStr('description'));
$repository->setDetail('encoding', $request->getStr('encoding'));
if (!$errors) {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository_id.'/basic/?saved=true');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
} else if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle('Changes Saved');
$error_view->appendChild('Repository changes were saved.');
}
$encoding_doc_link = PhabricatorEnv::getDoclink(
'article/User_Guide_UTF-8_and_Character_Encoding.html');
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/edit/'.$repository->getID().'/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($repository->getName())
->setError($e_name)
->setCaption('Human-readable repository name.'))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Description')
->setName('description')
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($repository->getDetail('description')))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Callsign')
->setName('callsign')
->setValue($repository->getCallsign()))
->appendChild(hsprintf('
<p class="aphront-form-instructions">'.
'If source code in this repository uses a character '.
'encoding other than UTF-8 (for example, ISO-8859-1), '.
'specify it here. You can usually leave this field blank. '.
'See User Guide: '.
'<a href="%s">UTF-8 and Character Encoding</a> for more information.'.
'</p>',
$encoding_doc_link))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Encoding')
->setName('encoding')
->setValue($repository->getDetail('encoding')))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Type')
->setName('type')
->setValue($repository->getVersionControlSystem()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('ID')
->setValue($repository->getID()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('PHID')
->setValue($repository->getPHID()))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save'));
$nav = $this->sideNav;
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Edit Repository'))
->setFormError($error_view)
->setForm($form);
$nav->appendChild($form_box);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Edit Repository'),
'device' => true,
));
}
private function processTrackingRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$errors = array();
$e_uri = null;
$e_path = null;
$is_git = false;
$is_svn = false;
$is_mercurial = false;
$e_ssh_key = null;
$e_ssh_keyfile = null;
$e_branch = null;
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$is_svn = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$is_mercurial = true;
break;
default:
throw new Exception("Unsupported VCS!");
}
$has_branches = ($is_git || $is_mercurial);
$has_local = ($is_git || $is_mercurial);
$has_branch_filter = ($is_git);
$has_auth_support = $is_svn;
if ($request->isFormPost()) {
$tracking = ($request->getStr('tracking') == 'enabled' ? true : false);
$repository->setDetail('tracking-enabled', $tracking);
$repository->setDetail('remote-uri', $request->getStr('uri'));
if ($has_local) {
$repository->setDetail('local-path', $request->getStr('path'));
}
if ($has_branch_filter) {
$branch_filter = $request->getStrList('branch-filter');
$branch_filter = array_fill_keys($branch_filter, true);
$repository->setDetail('branch-filter', $branch_filter);
$close_commits_filter = $request->getStrList('close-commits-filter');
$close_commits_filter = array_fill_keys($close_commits_filter, true);
$repository->setDetail('close-commits-filter', $close_commits_filter);
}
$repository->setDetail(
'disable-autoclose',
$request->getStr('autoclose') == 'disabled' ? true : false);
$repository->setDetail(
'pull-frequency',
max(1, $request->getInt('frequency')));
if ($has_branches) {
$repository->setDetail(
'default-branch',
$request->getStr('default-branch'));
if ($is_git) {
$branch_name = $repository->getDetail('default-branch');
if (strpos($branch_name, '/') !== false) {
$e_branch = 'Invalid';
$errors[] = "Your branch name should not specify an explicit ".
"remote. For instance, use 'master', not ".
"'origin/master'.";
}
}
}
$repository->setDetail(
'default-owners-path',
$request->getStr(
'default-owners-path',
'/'));
$repository->setDetail('ssh-login', $request->getStr('ssh-login'));
$repository->setDetail('ssh-key', $request->getStr('ssh-key'));
$repository->setDetail('ssh-keyfile', $request->getStr('ssh-keyfile'));
$repository->setDetail('http-login', $request->getStr('http-login'));
$repository->setDetail('http-pass', $request->getStr('http-pass'));
$repository->setDetail('show-user', $request->getInt('show-user'));
if ($repository->getDetail('ssh-key') &&
$repository->getDetail('ssh-keyfile')) {
$errors[] =
"Specify only one of 'SSH Private Key' and 'SSH Private Key File', ".
"not both.";
$e_ssh_key = 'Choose Only One';
$e_ssh_keyfile = 'Choose Only One';
}
$repository->setDetail(
'herald-disabled',
$request->getInt('herald-disabled', 0));
if ($is_svn) {
$repository->setUUID($request->getStr('uuid'));
$subpath = ltrim($request->getStr('svn-subpath'), '/');
if ($subpath) {
$subpath = rtrim($subpath, '/').'/';
}
$repository->setDetail('svn-subpath', $subpath);
}
if ($tracking) {
if (!$repository->getDetail('remote-uri')) {
$e_uri = 'Required';
$errors[] = "Repository URI is required.";
} else if ($is_svn &&
!preg_match('@/$@', $repository->getDetail('remote-uri'))) {
$e_uri = 'Invalid';
$errors[] = 'Subversion Repository Root must end in a slash ("/").';
} else {
$e_uri = null;
}
if ($has_local) {
if (!$repository->getDetail('local-path')) {
$e_path = 'Required';
$errors[] = "Local path is required.";
} else {
$e_path = null;
}
}
}
if (!$errors) {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository_id.'/tracking/?saved=true');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
} else if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle('Changes Saved');
$error_view->appendChild('Tracking changes were saved.');
} else if (!$repository->isTracked()) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_view->setTitle('Repository Not Tracked');
$error_view->appendChild(
'Tracking is currently "Disabled" for this repository, so it will '.
'not be imported into Phabricator. You can enable it below.');
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$is_svn = true;
break;
}
$doc_href = PhabricatorEnv::getDoclink('article/Diffusion_User_Guide.html');
$user_guide_link = phutil_tag(
'a',
array(
'href' => $doc_href,
),
'Diffusion User Guide');
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/edit/'.$repository->getID().'/tracking/')
->appendChild(hsprintf(
'<p class="aphront-form-instructions">Phabricator can track '.
'repositories, importing commits as they happen and notifying '.
'Differential, Diffusion, Herald, and other services. To enable '.
'tracking for a repository, configure it here and then start (or '.
'restart) the daemons. More information is available in the '.
'<strong>%s</strong>.</p>',
$user_guide_link));
$form
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Basics')
->appendChild(id(new AphrontFormStaticControl())
->setLabel('Repository Name')
->setValue($repository->getName()))
->appendChild(id(new AphrontFormSelectControl())
->setName('tracking')
->setLabel('Tracking')
->setOptions(array(
'disabled' => 'Disabled',
'enabled' => 'Enabled',
))
->setValue(
$repository->isTracked()
? 'enabled'
: 'disabled')));
$inset = new AphrontFormInsetView();
$inset->setTitle('Remote URI');
$clone_command = null;
$fetch_command = null;
if ($is_git) {
$clone_command = 'git clone';
$fetch_command = 'git fetch';
} else if ($is_mercurial) {
$clone_command = 'hg clone';
$fetch_command = 'hg pull';
}
$uri_label = 'Repository URI';
if ($has_local) {
if ($is_git) {
$instructions = hsprintf(
'Enter the URI to clone this repository from. It should look like '.
'<tt>git@github.com:example/example.git</tt>, '.
'<tt>ssh://user@host.com/git/example.git</tt>, or '.
'<tt>file:///local/path/to/repo</tt>');
} else if ($is_mercurial) {
$instructions = hsprintf(
'Enter the URI to clone this repository from. It should look '.
'something like <tt>ssh://user@host.com/hg/example</tt>');
}
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
$instructions));
} else if ($is_svn) {
$instructions = hsprintf(
'Enter the <strong>Repository Root</strong> for this SVN repository. '.
'You can figure this out by running <tt>svn info</tt> and looking at '.
'the value in the <tt>Repository Root</tt> field. It should be a URI '.
'and look like <tt>http://svn.example.org/svn/</tt>, '.
'<tt>svn+ssh://svn.example.com/svnroot/</tt>, or '.
'<tt>svn://svn.example.net/svn/</tt>');
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
$instructions));
$uri_label = 'Repository Root';
}
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('uri')
->setLabel($uri_label)
->setID('remote-uri')
->setValue($repository->getDetail('remote-uri'))
->setError($e_uri));
$inset->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'show-user',
1,
pht('Permit users to view the username of this connection.'),
$repository->getDetail('show-user') == 1));
$inset->appendChild(hsprintf(
'<div class="aphront-form-instructions">'.
'If you want to connect to this repository over SSH, enter the '.
'username and private key to use. You can leave these fields blank if '.
'the repository does not use SSH.'.
'</div>'));
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('ssh-login')
->setLabel('SSH User')
->setValue($repository->getDetail('ssh-login')))
->appendChild(
id(new AphrontFormTextAreaControl())
->setName('ssh-key')
->setLabel('SSH Private Key')
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($repository->getDetail('ssh-key'))
->setError($e_ssh_key)
->setCaption(
hsprintf('Specify the entire private key, <em>or</em>...')))
->appendChild(
id(new AphrontFormTextControl())
->setName('ssh-keyfile')
->setLabel('SSH Private Key File')
->setValue($repository->getDetail('ssh-keyfile'))
->setError($e_ssh_keyfile)
->setCaption(
'...specify a path on disk where the daemon should '.
'look for a private key.'));
if ($has_auth_support) {
$inset
->appendChild(hsprintf(
'<div class="aphront-form-instructions">'.
'If you want to connect to this repository with a username and '.
'password, such as over HTTP Basic Auth or SVN with SASL, '.
'enter the username and password to use. You can leave these '.
'fields blank if the repository does not use a username and '.
'password for authentication.'.
'</div>'))
->appendChild(
id(new AphrontFormTextControl())
->setName('http-login')
->setLabel('Username')
->setValue($repository->getDetail('http-login')))
->appendChild(
id(new AphrontFormPasswordControl())
->setName('http-pass')
->setLabel('Password')
->setValue($repository->getDetail('http-pass')));
}
$inset
->appendChild(hsprintf(
'<div class="aphront-form-important">'.
'To test your authentication configuration, <strong>save this '.
'form</strong> and then run this script:'.
'<code>'.
'phabricator/ $ ./scripts/repository/test_connection.php %s'.
'</code>'.
'This will verify that your configuration is correct and the '.
'daemons can connect to the remote repository and pull changes '.
'from it.'.
'</div>',
$repository->getCallsign()));
$form->appendChild($inset);
$inset = new AphrontFormInsetView();
$inset->setTitle('Repository Information');
if ($has_local) {
$default_local_path = '';
$default =
PhabricatorEnv::getEnvConfig('repository.default-local-path');
if (!$repository->getDetail('remote-uri') && $default) {
$default_local_path = $default.strtolower($repository->getCallsign());
}
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">Select a path on local disk '.
'which the daemons should <tt>%s</tt> the repository into. This must '.
'be readable and writable by the daemons, and readable by the '.
'webserver. The daemons will <tt>%s</tt> and keep this repository up '.
'to date.</p>',
$clone_command,
$fetch_command));
$inset->appendChild(
id(new AphrontFormTextControl())
->setName('path')
->setLabel('Local Path')
->setValue($repository->getDetail('local-path', $default_local_path))
->setError($e_path));
} else if ($is_svn) {
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">If you only want to parse one '.
'subpath of the repository, specify it here, relative to the '.
'repository root (e.g., <tt>trunk/</tt> or <tt>projects/wheel/</tt>). '.
'If you want to parse multiple subdirectories, create a separate '.
'Phabricator repository for each one.</p>'));
$inset->appendChild(
id(new AphrontFormTextControl())
->setName('svn-subpath')
->setLabel('Subpath')
->setValue($repository->getDetail('svn-subpath'))
->setError($e_path));
}
if ($has_branch_filter) {
$branch_filter_str = implode(
', ',
array_keys($repository->getDetail('branch-filter', array())));
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('branch-filter')
->setLabel('Track Only')
->setValue($branch_filter_str)
->setCaption(hsprintf(
'Optional list of branches to track. Other branches will be '.
'completely ignored. If left empty, all branches are tracked. '.
'Example: <tt>master, release</tt>')));
}
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('frequency')
->setLabel('Pull Frequency')
->setValue($repository->getDetail('pull-frequency', 15))
->setCaption(
'Number of seconds daemon should sleep between requests. Larger '.
'numbers reduce load but also decrease responsiveness.'));
$form->appendChild($inset);
$inset = new AphrontFormInsetView();
$inset->setTitle('Application Configuration');
if ($has_branches) {
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('default-branch')
->setLabel('Default Branch')
->setValue($repository->getDefaultBranch())
->setError($e_branch)
->setCaption(
'Default branch to show in Diffusion.'));
}
$inset
->appendChild(id(new AphrontFormSelectControl())
->setName('autoclose')
->setLabel('Autoclose')
->setOptions(array(
'enabled' => 'Enabled: Automatically Close Pushed Revisions',
'disabled' => 'Disabled: Ignore Pushed Revisions',
))
->setCaption(
"Automatically close Differential revisions when associated commits ".
"are pushed to this repository.")
->setValue(
$repository->getDetail('disable-autoclose', false)
? 'disabled'
: 'enabled'));
if ($has_branch_filter) {
$close_commits_filter_str = implode(
', ',
array_keys($repository->getDetail('close-commits-filter', array())));
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('close-commits-filter')
->setLabel('Autoclose Branches')
->setValue($close_commits_filter_str)
->setCaption(
'Optional list of branches which can trigger autoclose. '.
'If left empty, all branches trigger autoclose.'));
}
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('default-owners-path')
->setLabel('Default Owners Path')
->setValue(
$repository->getDetail(
'default-owners-path',
'/'))
->setCaption('Default path in Owners tool.'));
$inset
->appendChild(
id(new AphrontFormSelectControl())
->setName('herald-disabled')
->setLabel('Herald/Feed Enabled')
->setValue($repository->getDetail('herald-disabled', 0))
->setOptions(
array(
0 => 'Enabled - Send Email and Publish Stories',
1 => 'Disabled - Do Not Send Email or Publish Stories',
))
->setCaption(
'You can disable Herald commit notifications and feed stories '.
'for this repository. This can be useful when initially importing '.
'a repository. Feed stories are never published about commits '.
'that are more than 24 hours old.'));
if ($is_svn) {
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('uuid')
->setLabel('UUID')
->setValue($repository->getUUID())
->setCaption(hsprintf('Repository UUID from <tt>svn info</tt>.')));
}
$form->appendChild($inset);
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save Configuration'));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Edit Repository Tracking'))
->setFormError($error_view)
->setForm($form);
$nav = $this->sideNav;
$nav->appendChild($form_box);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Edit Repository Tracking'),
));
}
}

View file

@ -38,7 +38,7 @@ final class PhabricatorRepositoryListController
'a',
array(
'class' => 'button small grey',
'href' => '/repository/edit/'.$repo->getID().'/',
'href' => '/diffusion/'.$repo->getCallsign().'/edit/',
),
'Edit'),
);
@ -74,7 +74,7 @@ final class PhabricatorRepositoryListController
$panel = new AphrontPanelView();
$panel->setHeader('Repositories');
if ($is_admin) {
$panel->setCreateButton('Create New Repository', '/repository/create/');
$panel->setCreateButton('Create New Repository', '/diffusion/create/');
}
$panel->appendChild($table);
$panel->setNoBackground();

View file

@ -128,7 +128,8 @@ final class PhabricatorRepositoryPullLocalDaemon
if (!$no_discovery) {
// TODO: It would be nice to discover only if we pulled something,
// but this isn't totally trivial.
// but this isn't totally trivial. It's slightly more complicated
// with hosted repositories, too.
$lock_name = get_class($this).':'.$callsign;
$lock = PhabricatorGlobalLock::newLock($lock_name);
@ -200,28 +201,41 @@ final class PhabricatorRepositoryPullLocalDaemon
public function discoverRepository(PhabricatorRepository $repository) {
$vcs = $repository->getVersionControlSystem();
$result = null;
$refs = null;
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
return $this->executeGitDiscover($repository);
$result = $this->executeGitDiscover($repository);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$refs = $this->getDiscoveryEngine($repository)
->discoverCommits();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return $this->executeHgDiscover($repository);
$result = $this->executeHgDiscover($repository);
break;
default:
throw new Exception("Unknown VCS '{$vcs}'!");
}
foreach ($refs as $ref) {
$this->recordCommit(
$repository,
$ref->getIdentifier(),
$ref->getEpoch(),
$ref->getBranch());
if ($refs !== null) {
foreach ($refs as $ref) {
$this->recordCommit(
$repository,
$ref->getIdentifier(),
$ref->getEpoch(),
$ref->getBranch());
}
}
return (bool)count($refs);
$this->checkIfRepositoryIsFullyImported($repository);
if ($refs !== null) {
return (bool)count($refs);
} else {
return $result;
}
}
private function getDiscoveryEngine(PhabricatorRepository $repository) {
@ -455,7 +469,39 @@ final class PhabricatorRepositoryPullLocalDaemon
return $repository->getID().':'.$commit_identifier;
}
private function checkIfRepositoryIsFullyImported(
PhabricatorRepository $repository) {
// Check if the repository has the "Importing" flag set. We want to clear
// the flag if we can.
$importing = $repository->getDetail('importing');
if (!$importing) {
// This repository isn't marked as "Importing", so we're done.
return;
}
// Look for any commit which hasn't imported.
$unparsed_commit = queryfx_one(
$repository->establishConnection('r'),
'SELECT * FROM %T WHERE repositoryID = %d AND importStatus != %d',
id(new PhabricatorRepositoryCommit())->getTableName(),
$repository->getID(),
PhabricatorRepositoryCommit::IMPORTED_ALL);
if ($unparsed_commit) {
// We found a commit which still needs to import, so we can't clear the
// flag.
return;
}
// Clear the "importing" flag.
$repository->openTransaction();
$repository->beginReadLocking();
$repository = $repository->reload();
$repository->setDetail('importing', false);
$repository->save();
$repository->endReadLocking();
$repository->saveTransaction();
}
/* -( Git Implementation )------------------------------------------------- */
@ -487,6 +533,12 @@ final class PhabricatorRepositoryPullLocalDaemon
$stdout,
$only_this_remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE);
if (!$branches) {
// This repository has no branches at all, so we don't need to do
// anything. Generally, this means the repository is empty.
return;
}
$callsign = $repository->getCallsign();
$tracked_something = false;

View file

@ -6,6 +6,7 @@ final class PhabricatorRepositoryEditor
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorRepositoryTransaction::TYPE_VCS;
$types[] = PhabricatorRepositoryTransaction::TYPE_ACTIVATE;
$types[] = PhabricatorRepositoryTransaction::TYPE_NAME;
$types[] = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
@ -24,6 +25,10 @@ final class PhabricatorRepositoryEditor
$types[] = PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN;
$types[] = PhabricatorRepositoryTransaction::TYPE_HTTP_PASS;
$types[] = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH;
$types[] = PhabricatorRepositoryTransaction::TYPE_HOSTING;
$types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
$types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
$types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -36,6 +41,8 @@ final class PhabricatorRepositoryEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_VCS:
return $object->getVersionControlSystem();
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
return $object->isTracked();
case PhabricatorRepositoryTransaction::TYPE_NAME:
@ -72,6 +79,14 @@ final class PhabricatorRepositoryEditor
return $object->getDetail('http-pass');
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
return $object->getDetail('local-path');
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $object->isHosted();
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
return $object->getServeOverHTTP();
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
return $object->getServeOverSSH();
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
return $object->getPushPolicy();
}
}
@ -96,6 +111,11 @@ final class PhabricatorRepositoryEditor
case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS:
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
case PhabricatorRepositoryTransaction::TYPE_VCS:
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
return $xaction->getNewValue();
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
@ -108,6 +128,9 @@ final class PhabricatorRepositoryEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_VCS:
$object->setVersionControlSystem($xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
$object->setDetail('tracking-enabled', $xaction->getNewValue());
break;
@ -163,6 +186,14 @@ final class PhabricatorRepositoryEditor
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
$object->setDetail('local-path', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $object->setHosted($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
return $object->setServeOverHTTP($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
return $object->setServeOverSSH($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
return $object->setPushPolicy($xaction->getNewValue());
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
@ -219,4 +250,40 @@ final class PhabricatorRepositoryEditor
return parent::transactionHasEffect($object, $xaction);
}
protected function requireCapabilities(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
case PhabricatorRepositoryTransaction::TYPE_NAME:
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH:
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY:
case PhabricatorRepositoryTransaction::TYPE_UUID:
case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH:
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_SSH_KEY:
case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE:
case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS:
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
case PhabricatorRepositoryTransaction::TYPE_VCS:
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
}
}
}

View file

@ -54,7 +54,8 @@ abstract class PhabricatorRepositoryEngine {
if ($this->getVerbose()) {
$console = PhutilConsole::getConsole();
$argv = func_get_args();
call_user_func_array(array($console, 'writeLog'), $argv);
$argv[0] = $argv[0]."\n";
call_user_func_array(array($console, 'writeOut'), $argv);
}
return $this;
}

View file

@ -25,6 +25,16 @@ final class PhabricatorRepositoryPullEngine
$vcs = $repository->getVersionControlSystem();
$callsign = $repository->getCallsign();
if ($repository->isHosted()) {
$this->log(
pht(
'Repository "%s" is hosted, so Phabricator does not pull updates '.
'for it.',
$callsign));
return;
}
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// We never pull a local copy of Subversion repositories.
@ -89,7 +99,7 @@ final class PhabricatorRepositoryPullEngine
$repository = $this->getRepository();
$repository->execxRemoteCommand(
'clone --origin origin %s %s',
'clone --bare %s %s',
$repository->getRemoteURI(),
rtrim($repository->getLocalPath(), '/'));
}
@ -139,12 +149,11 @@ final class PhabricatorRepositoryPullEngine
$repo_path = rtrim($stdout, "\n");
if (empty($repo_path)) {
$err = true;
$message =
"Expected to find a git repository at '{$path}', but ".
"there was no result from `git rev-parse --show-toplevel`. ".
"Something is misconfigured or broken. The git repository ".
"may be inside a '.git/' directory.";
// This can mean one of two things: we're in a bare repository, or
// we're inside a git repository inside another git repository. Since
// the first is dramatically more likely now that we perform bare
// clones and I don't have a great way to test for the latter, assume
// we're OK.
} else if (!Filesystem::pathsAreEquivalent($repo_path, $path)) {
$err = true;
$message =

View file

@ -8,7 +8,7 @@ final class PhabricatorWorkingCopyPullTestCase
$this->assertEqual(
true,
Filesystem::pathExists($repo->getLocalPath().'/.git'));
Filesystem::pathExists($repo->getLocalPath().'/HEAD'));
}
public function testHgPullBasic() {

View file

@ -45,7 +45,8 @@ abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase {
$dir = PhutilDirectoryFixture::newFromArchive($path);
$local = new TempFile('.ignore');
$repo = id(new PhabricatorRepository())
$user = $this->generateNewTestUser();
$repo = PhabricatorRepository::initializeNewRepository($user)
->setCallsign($callsign)
->setName(pht('Test Repo "%s"', $callsign))
->setVersionControlSystem($vcs_type)

View file

@ -0,0 +1,62 @@
<?php
/**
* In Git, there appears to be no way to send a message which will be output
* by `git clone http://...`, although the response code is visible.
*
* In Mercurial, the HTTP status response message is printed to the console, so
* we send human-readable text there.
*
* In Subversion, we can get it to print a custom message if we send an
* invalid/unknown response code, although the output is ugly and difficult
* to read. For known codes like 404, it prints a canned message.
*
* All VCS binaries ignore the response body; we include it only for
* completeness.
*/
final class PhabricatorVCSResponse extends AphrontResponse {
private $code;
private $message;
public function __construct($code, $message) {
$this->code = $code;
$message = head(phutil_split_lines($message));
$this->message = $message;
}
public function getMessage() {
return $this->message;
}
public function buildResponseString() {
return $this->code.' '.$this->message;
}
public function getHeaders() {
$headers = array();
if ($this->getHTTPResponseCode() == 401) {
$headers[] = array(
'WWW-Authenticate',
'Basic realm="Phabricator Repositories"',
);
}
return $headers;
}
public function getCacheHeaders() {
return array();
}
public function getHTTPResponseCode() {
return $this->code;
}
public function getHTTPResponseMessage() {
return $this->message;
}
}

View file

@ -26,11 +26,16 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
const TABLE_BADCOMMIT = 'repository_badcommit';
const TABLE_LINTMESSAGE = 'repository_lintmessage';
const SERVE_OFF = 'off';
const SERVE_READONLY = 'readonly';
const SERVE_READWRITE = 'readwrite';
protected $name;
protected $callsign;
protected $uuid;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_ADMIN;
protected $viewPolicy;
protected $editPolicy;
protected $pushPolicy;
protected $versionControlSystem;
protected $details = array();
@ -40,6 +45,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
private $commitCount = self::ATTACHABLE;
private $mostRecentCommit = self::ATTACHABLE;
public static function initializeNewRepository(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorApplicationDiffusion'))
->executeOne();
$view_policy = $app->getPolicy(DiffusionCapabilityDefaultView::CAPABILITY);
$edit_policy = $app->getPolicy(DiffusionCapabilityDefaultEdit::CAPABILITY);
$push_policy = $app->getPolicy(DiffusionCapabilityDefaultPush::CAPABILITY);
return id(new PhabricatorRepository())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setPushPolicy($push_policy);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@ -171,24 +192,32 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
}
public function execLocalCommand($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('exec_manual', $args);
}
public function execxLocalCommand($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('execx', $args);
}
public function getLocalCommandFuture($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return newv('ExecFuture', $args);
}
public function passthruLocalCommand($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('phutil_passthru', $args);
@ -412,6 +441,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
}
public function shouldAutocloseBranch($branch) {
if ($this->isImporting()) {
return false;
}
if ($this->getDetail('disable-autoclose', false)) {
return false;
}
@ -465,6 +498,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return 'r'.$this->getCallsign().$short_identifier;
}
public function isImporting() {
return (bool)$this->getDetail('importing', false);
}
/* -( Repository URI Management )------------------------------------------ */
@ -691,6 +728,64 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
}
public function isHosted() {
return (bool)$this->getDetail('hosting-enabled', false);
}
public function setHosted($enabled) {
return $this->setDetail('hosting-enabled', $enabled);
}
public function getServeOverHTTP() {
return $this->getDetail('serve-over-http', self::SERVE_OFF);
}
public function setServeOverHTTP($mode) {
return $this->setDetail('serve-over-http', $mode);
}
public function getServeOverSSH() {
return $this->getDetail('serve-over-ssh', self::SERVE_OFF);
}
public function setServeOverSSH($mode) {
return $this->setDetail('serve-over-ssh', $mode);
}
public static function getProtocolAvailabilityName($constant) {
switch ($constant) {
case self::SERVE_OFF:
return pht('Off');
case self::SERVE_READONLY:
return pht('Read Only');
case self::SERVE_READWRITE:
return pht('Read/Write');
default:
return pht('Unknown');
}
}
/**
* Raise more useful errors when there are basic filesystem problems.
*/
private function assertLocalExists() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
if (!$this->isHosted()) {
// For non-hosted SVN repositories, we don't expect a local directory
// to exist.
return;
}
break;
}
$local = $this->getLocalPath();
Filesystem::assertExists($local);
Filesystem::assertIsDirectory($local);
Filesystem::assertReadable($local);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -699,6 +794,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
DiffusionCapabilityPush::CAPABILITY,
);
}
@ -708,6 +804,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
case DiffusionCapabilityPush::CAPABILITY:
return $this->getPushPolicy();
}
}

View file

@ -15,6 +15,13 @@ final class PhabricatorRepositoryCommit
protected $authorPHID;
protected $auditStatus = PhabricatorAuditCommitStatusConstants::NONE;
protected $summary = '';
protected $importStatus = 0;
const IMPORTED_MESSAGE = 1;
const IMPORTED_CHANGE = 2;
const IMPORTED_OWNERS = 4;
const IMPORTED_HERALD = 8;
const IMPORTED_ALL = 15;
private $commitData = self::ATTACHABLE;
private $audits;
@ -39,6 +46,20 @@ final class PhabricatorRepositoryCommit
return $this->isUnparsed;
}
public function isImported() {
return ($this->getImportStatus() == self::IMPORTED_ALL);
}
public function writeImportStatusFlag($flag) {
queryfx(
$this->establishConnection('w'),
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
$this->getTableName(),
$flag,
$this->getID());
return $this;
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,

View file

@ -3,6 +3,7 @@
final class PhabricatorRepositoryTransaction
extends PhabricatorApplicationTransaction {
const TYPE_VCS = 'repo:vcs';
const TYPE_ACTIVATE = 'repo:activate';
const TYPE_NAME = 'repo:name';
const TYPE_DESCRIPTION = 'repo:description';
@ -21,6 +22,10 @@ final class PhabricatorRepositoryTransaction
const TYPE_HTTP_LOGIN = 'repo:http-login';
const TYPE_HTTP_PASS = 'repo:http-pass';
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';
public function getApplicationName() {
return 'repository';
@ -34,6 +39,64 @@ final class PhabricatorRepositoryTransaction
return null;
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_PUSH_POLICY:
$phids[] = $old;
$phids[] = $new;
break;
}
return $phids;
}
public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_REMOTE_URI:
case self::TYPE_SSH_LOGIN:
case self::TYPE_SSH_KEY:
case self::TYPE_SSH_KEYFILE:
case self::TYPE_HTTP_LOGIN:
case self::TYPE_HTTP_PASS:
// Hide null vs empty string changes.
return (!strlen($old) && !strlen($new));
case self::TYPE_LOCAL_PATH:
case self::TYPE_NAME:
// Hide these on create, they aren't interesting and we have an
// explicit "create" transaction.
if (!strlen($old)) {
return true;
}
break;
}
return parent::shouldHide();
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_VCS:
return 'create';
}
return parent::getIcon();
}
public function getColor() {
switch ($this->getTransactionType()) {
case self::TYPE_VCS:
return 'green';
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
@ -41,6 +104,10 @@ final class PhabricatorRepositoryTransaction
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_VCS:
return pht(
'%s created this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_ACTIVATE:
if ($new) {
return pht(
@ -238,6 +305,36 @@ final class PhabricatorRepositoryTransaction
$this->renderHandleLink($author_phid),
$old,
$new);
case self::TYPE_HOSTING:
if ($new) {
return pht(
'%s changed this repository to be hosted on Phabricator.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed this repository to track a remote elsewhere.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_PROTOCOL_HTTP:
return pht(
'%s changed the availability of this repository over HTTP from '.
'"%s" to "%s".',
$this->renderHandleLink($author_phid),
PhabricatorRepository::getProtocolAvailabilityName($old),
PhabricatorRepository::getProtocolAvailabilityName($new));
case self::TYPE_PROTOCOL_SSH:
return pht(
'%s changed the availability of this repository over SSH from '.
'"%s" to "%s".',
$this->renderHandleLink($author_phid),
PhabricatorRepository::getProtocolAvailabilityName($old),
PhabricatorRepository::getProtocolAvailabilityName($new));
case self::TYPE_PUSH_POLICY:
return pht(
'%s changed the push policy of this repository from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
}
return parent::getTitle();
@ -263,6 +360,5 @@ final class PhabricatorRepositoryTransaction
return $view->render();
}
}

View file

@ -12,6 +12,25 @@ final class PhabricatorRepositoryCommitHeraldWorker
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$result = $this->applyHeraldRules($repository, $commit);
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_HERALD);
return $result;
}
private function applyHeraldRules(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
// Don't take any actions on an importing repository. Principally, this
// avoids generating thousands of audits or emails when you import an
// established repository on an existing install.
if ($repository->isImporting()) {
return;
}
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());

View file

@ -58,6 +58,9 @@ final class PhabricatorRepositoryCommitOwnersWorker
$commit->save();
}
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_OWNERS);
if ($this->shouldQueueFollowupTasks()) {
PhabricatorWorker::scheduleTask(
'PhabricatorRepositoryCommitHeraldWorker',

View file

@ -58,6 +58,9 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker
protected function finishParse() {
$commit = $this->commit;
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_CHANGE);
id(new PhabricatorSearchIndexer())
->indexDocumentByPHID($commit->getPHID());

View file

@ -220,6 +220,9 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker
}
$data->save();
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_MESSAGE);
}
private function loadUserName($user_phid, $default, PhabricatorUser $actor) {
@ -249,7 +252,17 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker
->loadRawDiff();
// TODO: Support adds, deletes and moves under SVN.
$changes = id(new ArcanistDiffParser())->parseDiff($raw_diff);
if (strlen($raw_diff)) {
$changes = id(new ArcanistDiffParser())->parseDiff($raw_diff);
} else {
// This is an empty diff, maybe made with `git commit --allow-empty`.
// NOTE: These diffs have the same tree hash as their ancestors, so
// they may attach to revisions in an unexpected way. Just let this
// happen for now, although it might make sense to special case it
// eventually.
$changes = array();
}
$diff = DifferentialDiff::newFromRawChanges($changes)
->setRevisionID($revision->getID())
->setAuthorPHID($actor_phid)

View file

@ -16,7 +16,7 @@ final class PhabricatorSettingsPanelSSHKeys
}
public function isEnabled() {
return PhabricatorEnv::getEnvConfig('auth.sshkeys.enabled');
return true;
}
public function processRequest(AphrontRequest $request) {

View file

@ -4,6 +4,16 @@ abstract class PhabricatorSSHWorkflow extends PhutilArgumentWorkflow {
private $user;
private $iochannel;
private $errorChannel;
public function setErrorChannel(PhutilChannel $error_channel) {
$this->errorChannel = $error_channel;
return $this;
}
public function getErrorChannel() {
return $this->errorChannel;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
@ -27,6 +37,50 @@ abstract class PhabricatorSSHWorkflow extends PhutilArgumentWorkflow {
return $this->iochannel;
}
public function passthruIO(ExecFuture $future) {
$exec_channel = new PhutilExecChannel($future);
$exec_channel->setStderrHandler(array($this, 'writeErrorIOCallback'));
$io_channel = $this->getIOChannel();
$error_channel = $this->getErrorChannel();
$channels = array($exec_channel, $io_channel, $error_channel);
while (true) {
PhutilChannel::waitForAny($channels);
$io_channel->update();
$exec_channel->update();
$error_channel->update();
$done = !$exec_channel->isOpen();
$data = $io_channel->read();
if (strlen($data)) {
$exec_channel->write($data);
}
$data = $exec_channel->read();
if (strlen($data)) {
$io_channel->write($data);
}
// If we have nothing left on stdin, close stdin on the subprocess.
if (!$io_channel->isOpenForReading()) {
// TODO: This should probably be part of PhutilExecChannel?
$future->write('');
}
if ($done) {
break;
}
}
list($err) = $future->resolve();
return $err;
}
public function readAllInput() {
$channel = $this->getIOChannel();
while ($channel->update()) {
@ -38,4 +92,18 @@ abstract class PhabricatorSSHWorkflow extends PhutilArgumentWorkflow {
return $channel->read();
}
public function writeIO($data) {
$this->getIOChannel()->write($data);
return $this;
}
public function writeErrorIO($data) {
$this->getErrorChannel()->write($data);
return $this;
}
public function writeErrorIOCallback(PhutilChannel $channel, $data) {
$this->writeErrorIO($data);
}
}

View file

@ -1704,6 +1704,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20131020.harbormaster.sql'),
),
'20131025.repopush.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131025.repopush.sql'),
),
'20131026.commitstatus.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131026.commitstatus.sql'),
),
);
}
}

View file

@ -26,7 +26,18 @@ final class AphrontFormPolicyControl extends AphrontFormControl {
PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),
);
$this->setLabel(idx($labels, $this->capability, pht('Unknown Policy')));
if (isset($labels[$capability])) {
$label = $labels[$capability];
} else {
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
if ($capobj) {
$label = $capobj->getCapabilityName();
} else {
$label = pht('Capability "%s"', $capability);
}
}
$this->setLabel($label);
return $this;
}