Initial implementation of gitolite integration script
Is based on old gitosis/gitadmin script, but it is heavily refactored in the process of migration.
This commit is contained in:
483
scripts/gitolite/rebuild_gitolite.php
Executable file
483
scripts/gitolite/rebuild_gitolite.php
Executable file
@@ -0,0 +1,483 @@
|
||||
#!/usr/local/bin/php
|
||||
<?php
|
||||
|
||||
$root = dirname(dirname(dirname(__FILE__)));
|
||||
require_once $root.'/scripts/__init_script__.php';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Utilities.
|
||||
//
|
||||
// TODO(sergey): Move somewhere else. Or, evenmore ideally, use Phabricator's
|
||||
// utilities instead.
|
||||
|
||||
function escape_name($name) {
|
||||
return preg_replace('/[^A-Za-z0-9\-]/', '_', $name);
|
||||
}
|
||||
|
||||
function startswith($string, $prefix) {
|
||||
return substr($string, 0, strlen($prefix)) == $prefix;
|
||||
}
|
||||
|
||||
function endswith($string, $suffix) {
|
||||
$suffix_length = strlen($suffix);
|
||||
return substr($string, strlen($string) - $suffix_length,
|
||||
$suffix_length) == $suffix;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Phabricator access list traversal.
|
||||
|
||||
class Configuration {
|
||||
// Phabricator user which is used as a viewer.
|
||||
public $viewer;
|
||||
|
||||
// Directory where public keys are stored.
|
||||
// Full path.
|
||||
protected $keys_directory;
|
||||
|
||||
// Gitolite configuration file (gitolite.conf).
|
||||
// Full path.
|
||||
protected $config_file;
|
||||
|
||||
// Indexed by key content, contains configuration user name.
|
||||
protected $system_keys;
|
||||
|
||||
// Indexed by config user name.
|
||||
protected $used_keys;
|
||||
|
||||
// Indexed by committers variable name, contains list of users which are
|
||||
// configured by Phabricator to be able to commit to the repository.
|
||||
protected $committers;
|
||||
|
||||
public function __construct($gitolite_root) {
|
||||
$this->viewer = PhabricatorUser::getOmnipotentUser();
|
||||
$this->keys_directory = "$gitolite_root/keydir";
|
||||
$this->config_file = "$gitolite_root/conf/gitolite.conf";
|
||||
|
||||
$this->system_keys = $this->getSystemPublicKeys();
|
||||
|
||||
if (!file_exists($this->config_file)) {
|
||||
die("Not found: $this->config_file\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Store given key of given user.
|
||||
//
|
||||
// Includes both storing public key in the file, and storing mapping between
|
||||
// user and the key.
|
||||
public function storeUserPublicKey($user, $key) {
|
||||
$full_key_content = $this->getPublicKeyContent($key);
|
||||
|
||||
if (array_key_exists($full_key_content, $this->system_keys)) {
|
||||
return $this->system_keys[$full_key_content];
|
||||
}
|
||||
|
||||
$config_user_name = $this->getConfigUserName($user, $key);
|
||||
|
||||
if (!array_key_exists($config_user_name, $this->used_keys)) {
|
||||
$this->used_keys[$config_user_name] = true;
|
||||
file_put_contents("$this->keys_directory/$config_user_name.pub",
|
||||
$full_key_content);
|
||||
}
|
||||
|
||||
return $config_user_name;
|
||||
}
|
||||
|
||||
public function setRepositoryUsers($repository, $config_user_names) {
|
||||
$uri = $repository->getRemoteURI();
|
||||
$repository_name = basename($uri, '.git');
|
||||
|
||||
$variable_name = '@committers_' . escape_name(strtolower($repository_name));
|
||||
$this->committers[$variable_name] = $config_user_names;
|
||||
}
|
||||
|
||||
public function writeNewConfiguration() {
|
||||
$current_config = file_get_contents($this->config_file);
|
||||
$current_config_lines = explode("\n", $current_config);
|
||||
|
||||
$new_config = "";
|
||||
foreach ($current_config_lines as $line) {
|
||||
if (startswith($line, '@committers_')) {
|
||||
$parts = explode('=', $line);
|
||||
$variable_name = trim($parts[0]);
|
||||
if (array_key_exists($variable_name, $this->committers)) {
|
||||
$system_committers = $this->getNonPhabtricatorUsers($parts[1]);
|
||||
$committers = implode(' ', array_merge(
|
||||
$system_committers, $this->committers[$variable_name]));
|
||||
$line = "$variable_name = $committers";
|
||||
}
|
||||
}
|
||||
$new_config .= $line . "\n";
|
||||
}
|
||||
|
||||
file_put_contents($this->config_file, $new_config);
|
||||
}
|
||||
|
||||
protected function getNonPhabtricatorUsers($configuration_value) {
|
||||
$system_users = array();
|
||||
$users = explode(' ', $configuration_value);
|
||||
foreach ($users as $user) {
|
||||
$user = trim($user);
|
||||
if (empty($user)) {
|
||||
continue;
|
||||
}
|
||||
if (startswith($user, 'PHAB')) {
|
||||
continue;
|
||||
}
|
||||
$system_users[] = $user;
|
||||
}
|
||||
return $system_users;
|
||||
}
|
||||
|
||||
public function finalize() {
|
||||
$this->removeUnusedPublicKeys();
|
||||
}
|
||||
|
||||
// Get content of a public key to be stored in file.
|
||||
protected function getPublicKeyContent($key) {
|
||||
return $key->getKeyType().' '.
|
||||
$key->getKeyBody().' '.
|
||||
$key->getKeyComment()."\n";
|
||||
}
|
||||
|
||||
// Get user+key name used by the Gitolite configuration.
|
||||
protected function getConfigUserName($user, $key) {
|
||||
$escaped_key_name = escape_name($key->getName());
|
||||
return 'PHAB_'.$user->getUserName().
|
||||
'_'.$escaped_key_name.
|
||||
'_'.$key->getID();
|
||||
}
|
||||
|
||||
// Get keys which are not managed by this Phabricator/Git integration script.
|
||||
//
|
||||
// Returns map from key content to the key file name. This is used to avoid
|
||||
// public key duplication in the case system key is used by phabricator user.
|
||||
protected function getSystemPublicKeys() {
|
||||
$files = scandir($this->keys_directory);
|
||||
$system_keys = array();
|
||||
foreach ($files as $file) {
|
||||
if (startswith($file, "PHAB")) {
|
||||
continue;
|
||||
}
|
||||
if (!endswith($file, '.pub')) {
|
||||
continue;
|
||||
}
|
||||
$key = file_get_contents("$this->keys_directory/$file");
|
||||
$file_we = basename($file, '.pub');
|
||||
$this->system_keys[$key] = $file_we;
|
||||
$this->used_keys[$file_we] = true;
|
||||
}
|
||||
return $system_keys;
|
||||
}
|
||||
|
||||
protected function removeUnusedPublicKeys() {
|
||||
$files = scandir($this->keys_directory);
|
||||
foreach ($files as $file) {
|
||||
if (!startswith($file, "PHAB")) {
|
||||
continue;
|
||||
}
|
||||
$config_user_name = basename($file, '.pub');
|
||||
if (!array_key_exists($config_user_name, $this->used_keys)) {
|
||||
unlink("$this->keys_directory/$file");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getProjectMembersPHIDs($viewer, $project_phid) {
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->needMembers(true)
|
||||
->withPHIDs(array($project_phid))
|
||||
->executeOne();
|
||||
|
||||
return $project->getMemberPHIDs();
|
||||
}
|
||||
|
||||
// Get user's heys and put them to the configuration
|
||||
function handleSingleUserPHID($config, $userPHID) {
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($config->viewer)
|
||||
->withPHIDs(array($userPHID))
|
||||
->executeOne();
|
||||
if (!$user) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ($user->getIsDisabled()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
|
||||
'objectPHID = %s',
|
||||
$user->getPHID());
|
||||
|
||||
$config_user_names = array();
|
||||
foreach ($keys as $key) {
|
||||
$config_user_name = $config->storeUserPublicKey($user, $key);
|
||||
$config_user_names[] = $config_user_name;
|
||||
}
|
||||
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleUsersPolicyRule($config, $rule) {
|
||||
$config_user_names = array();
|
||||
foreach ($rule['value'] as $userPHID) {
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleSingleUserPHID($config, $userPHID));
|
||||
}
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleProjectsPolicyRule($config, $rule) {
|
||||
$config_user_names = array();
|
||||
foreach ($rule['value'] as $projectPHID) {
|
||||
$memberPHIDs = getProjectMembersPHIDs($config->viewer, $projectPHID);
|
||||
foreach ($memberPHIDs as $userPHID) {
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleSingleUserPHID($config, $userPHID));
|
||||
}
|
||||
}
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleProjectsAllPolicyRule($config, $rule) {
|
||||
$is_first_project = true;
|
||||
$allowed_members_phids = array();
|
||||
foreach ($rule['value'] as $project_phid) {
|
||||
$memberPHIDs = getProjectMembersPHIDs($config->viewer, $project_phid);
|
||||
if ($is_first_project) {
|
||||
$allowed_members_phids = $memberPHIDs;
|
||||
$is_first_project = false;
|
||||
} else {
|
||||
$allowed_members_phids = array_intersect(
|
||||
$allowed_members_phids, $memberPHIDs);
|
||||
}
|
||||
}
|
||||
|
||||
$config_user_names = array();
|
||||
foreach ($allowed_members_phids as $userPHID) {
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleSingleUserPHID($config, $userPHID));
|
||||
}
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleAdministratorsPolicyRule($config, $rule) {
|
||||
$administrators = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($config->viewer)
|
||||
->withIsAdmin(true)
|
||||
->execute();
|
||||
|
||||
$config_user_names = array();
|
||||
foreach ($administrators as $administrator) {
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleSingleUserPHID($config, $administrator->getPHID()));
|
||||
}
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleLegalpadSingleDocument($config, $document) {
|
||||
if ($document->getSignatureType() !=
|
||||
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$config_user_names = array();
|
||||
foreach ($document->getSignatures() as $signature) {
|
||||
if ($signature->getSignatureType() !=
|
||||
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
|
||||
continue;
|
||||
}
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleSingleUserPHID($config, $signature->getSignerPHID()));
|
||||
}
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleLegalpadSignaturePolicyRule($config, $rule) {
|
||||
$documents = id(new LegalpadDocumentQuery())
|
||||
->setViewer($config->viewer)
|
||||
->withPHIDs($rule['value'])
|
||||
->needSignatures(true)
|
||||
->execute();
|
||||
|
||||
$config_user_names = array();
|
||||
foreach ($documents as $document) {
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleLegalpadSingleDocument($config, $document));
|
||||
}
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
function handleCustomPolicy($config, $policy) {
|
||||
$config_user_names = array();
|
||||
$rules = $policy->getRules();
|
||||
foreach ($rules as $rule) {
|
||||
// Everyone is denied by default anyway
|
||||
if ($rule['action'] != 'allow') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$policy_config_user_names = array();
|
||||
$rule_type = $rule['rule'];
|
||||
if ($rule_type == 'PhabricatorPolicyRuleUsers') {
|
||||
$policy_config_user_names =
|
||||
handleUsersPolicyRule($system_keys, $used_keys);
|
||||
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
|
||||
$policy_config_user_names =
|
||||
handleProjectsPolicyRule($system_keys, $used_keys);
|
||||
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
|
||||
$policy_config_user_names =
|
||||
handleProjectsAllPolicyRule($system_keys, $used_keys);
|
||||
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
|
||||
$policy_config_user_names =
|
||||
handleAdministratorsPolicyRule($system_keys, $used_keys);
|
||||
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
|
||||
$policy_config_user_names =
|
||||
handleLegalpadSignaturePolicyRule($system_keys, $used_keys);
|
||||
}
|
||||
|
||||
$config_user_names = array_merge(
|
||||
$config_user_names, $policy_config_user_names);
|
||||
}
|
||||
|
||||
return $config_user_names;
|
||||
}
|
||||
|
||||
// Parse repository and put it's members to the config file
|
||||
function handleSingleRepository($config, $repository) {
|
||||
$policies = PhabricatorPolicyQuery::loadPolicies(
|
||||
$config->viewer,
|
||||
$repository);
|
||||
|
||||
$pushable = $policies[DiffusionPushCapability::CAPABILITY];
|
||||
$type = $pushable->getType();
|
||||
|
||||
$config_user_names = array();
|
||||
|
||||
if ($type == PhabricatorPolicyType::TYPE_PROJECT) {
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($config->viewer)
|
||||
->needMembers(true)
|
||||
->withPHIDs(array($pushable->getPHID()))
|
||||
->executeOne();
|
||||
|
||||
$memberPHIDs = $project->getMemberPHIDs();
|
||||
foreach ($memberPHIDs as $memberPHID) {
|
||||
$config_user_names = array_merge($config_user_names,
|
||||
handleSingleUserPHID($config, $memberPHID));
|
||||
}
|
||||
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
|
||||
$config_user_names = handleSingleUserPHID($config, $pushable->getPHID());
|
||||
} else if ($type == PhabricatorPolicyType::TYPE_CUSTOM) {
|
||||
$config_user_names = handleCustomPolicy($config, $pushable);
|
||||
} else {
|
||||
/* pass */
|
||||
}
|
||||
|
||||
$config->setRepositoryUsers($repository, $config_user_names);
|
||||
}
|
||||
|
||||
function rebuildConfiguration($gitolite_root) {
|
||||
$config = new Configuration($gitolite_root);
|
||||
|
||||
// Fill in new configuration and keys
|
||||
$used_keys = array();
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($config->viewer)
|
||||
->execute();
|
||||
|
||||
foreach ($repositories as $repository_id => $repository) {
|
||||
$type = $repository->getVersionControlSystem();
|
||||
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
|
||||
handleSingleRepository($config, $repository);
|
||||
}
|
||||
}
|
||||
|
||||
$config->writeNewConfiguration();
|
||||
|
||||
$config->finalize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Repository manipulation functionality.
|
||||
|
||||
function getGitCommand($repository) {
|
||||
$git_dir = realpath("$repository/.git");
|
||||
$git = "git --git-dir='$git_dir'";
|
||||
$git .= ' --work-tree='.realpath($repository);
|
||||
return $git;
|
||||
}
|
||||
|
||||
function runGitCommand($repository, $arguments,
|
||||
&$output=null, &$return_var=null) {
|
||||
$git = getGitCommand($repository);
|
||||
$git .= " $arguments";
|
||||
exec($git, $output, $return_var);
|
||||
return $return_var == 0;
|
||||
}
|
||||
|
||||
function runGitSshCommand($repository, $key, $arguments,
|
||||
&$output=null, &$return_var=null) {
|
||||
$abs_key = realpath($key);
|
||||
$git = "GIT_SSH_COMMAND=\"ssh -i $key -o IdentitiesOnly=yes\" ";
|
||||
$git .= getGitCommand($repository);
|
||||
$git .= " $arguments";
|
||||
exec($git, $output, $return_var);
|
||||
return $return_var == 0;
|
||||
}
|
||||
|
||||
function repositoryPull($repository, $key) {
|
||||
return runGitSshCommand($repository, $key, 'pull');
|
||||
}
|
||||
|
||||
function repositoryCommitAll($repository, $author, $message) {
|
||||
if (!runGitCommand(
|
||||
$repository, 'ls-files --other --exclude-standard', $untracked_files)) {
|
||||
return false;
|
||||
}
|
||||
if (count($untracked_files)) {
|
||||
$flat_files = join(' ', $untracked_files);
|
||||
if (!runGitCommand($repository, "add $flat_files")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
runGitCommand($repository, "update-index -q --refresh", $output);
|
||||
runGitCommand($repository, "diff-index --name-only HEAD --", $output);
|
||||
if (count($output)) {
|
||||
return runGitCommand(
|
||||
$repository, "commit --author='$author' -a -m '$message'", $output);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($argv) != 3) {
|
||||
print("Usage: {$argv[0]} /path/to/gitolite-admin /path/to/id_rsa.pub\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$gitolite_root = $argv[1];
|
||||
$key = $argv[2];
|
||||
|
||||
if (!repositoryPull($gitolite_root, $key)) {
|
||||
print("Failed to pull changes from server.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!rebuildConfiguration($gitolite_root)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!repositoryCommitAll(
|
||||
$gitolite_root, 'Rebuild Gitadmin <null@git.blender.org>',
|
||||
'Update to correspond changes in Phabricator')) {
|
||||
print("Failed to commit changes.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
runGitSshCommand($gitolite_root, $key, 'push origin master');
|
||||
?>
|
||||
Reference in New Issue
Block a user