Files
phabricator/scripts/gitadmin/rebuild_gitadmin.php
Sergey Sharybin b1097e9c61 Fix for crash when pushable project have unknown users
Apparently we've got Unknown Object (Phabricator User) in
translation project now.

Not sure how it became invalid, but automated scripts
better be robust for this.
2013-11-27 22:14:07 +06:00

291 lines
8.1 KiB
PHP
Executable File

#!/usr/local/bin/php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
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;
}
function write_ini_file($array, $file) {
$res = array();
foreach ($array as $key => $val) {
if (is_array($val)) {
$res[] = "[$key]";
foreach ($val as $skey => $sval) {
$res[] = "$skey = $sval";
}
$res[] = '';
} else {
$res[] = "$key = $val";
}
}
file_put_contents($file, implode("\n", $res));
}
// Get user's heys and put them to the configuration
function handleSingleUserPHID(
$keydir, $viewer, $userPHID, $system_keys, &$used_keys) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($userPHID))
->executeOne();
if (!$user) {
return;
}
$keys = id(new PhabricatorUserSSHKey())->loadAllWhere(
'userPHID = %s',
$user->getPHID());
$members = array();
foreach ($keys as $key) {
$full_key_content =
$key->getKeyType().' '.
$key->getKeyBody().' '.
$key->getKeyComment()."\n";
if (array_key_exists($full_key_content, $system_keys)) {
$members[] = $system_keys[$full_key_content];
} else {
$escaped_key_name = escape_name($key->getName());
$member = 'PHAB_'.$user->getUserName().
'_'.$escaped_key_name.
'_'.$key->getID();
$members[] = $member;
if (!array_key_exists($member, $used_keys)) {
$used_keys[$member] = true;
file_put_contents("$keydir/$member.pub", $full_key_content);
}
}
}
return $members;
}
// Parse repository and put it's members to the config file
function handleSingleRepository(
$keydir, $viewer, $repository, $all_repositories, $system_keys,
&$new_configuration, &$used_keys) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$viewer,
$repository);
$pushable = $policies[DiffusionCapabilityPush::CAPABILITY];
$type = phid_get_type($pushable->getPHID());
$members = array();
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($pushable->getPHID()))
->executeOne();
$memberPHIDs = $project->getMemberPHIDs();
foreach ($memberPHIDs as $memberPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $memberPHID,
$system_keys, $used_keys));
}
} else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
$members = handleSingleUserPHID(
$keydir, $viewer, $pushable->getPHID(), $system_keys, $used_keys);
} else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
/* pass */
} else {
/* pass */
}
if (count($members)) {
$uri = $repository->getRemoteURI();
$repository_name = basename($uri, '.git');
$escaped_repository_name = escape_name($repository->getName());
$group_name = "PHAB_${escaped_repository_name}";
$values = array();
$values['members'] = join(' ', $members);
$values['readonly'] = join(' ', $all_repositories);
$values['writable'] = $repository_name;
$new_configuration["group $group_name"] = $values;
}
}
// Remove groups from previous automated configuration built
function getCleanOldConfiguration($old_configuration) {
$new_configuration = array();
foreach ($old_configuration as $group => $values) {
if (!startswith($group, 'group PHAB')) {
$new_configuration[$group] = $values;
}
}
return $new_configuration;
}
// Get non-phab keys
function getSystemPublicKeys($keydir) {
$files = scandir($keydir);
$system_keys = array();
foreach ($files as $file) {
if (!startswith($file, "PHAB") && endswith($file, '.pub')) {
$key = file_get_contents("$keydir/$file");
$system_keys[$key] = basename($file, '.pub');
}
}
return $system_keys;
}
// Remove unused public keys
function removeUnusedPublicKeys($keydir, $used_keys) {
$files = scandir($keydir);
foreach ($files as $file) {
if (startswith($file, "PHAB")) {
$member = basename($file, '.pub');
if (!array_key_exists($member, $used_keys)) {
unlink("$keydir/$file");
}
}
}
}
function rebuildConfiguration($gitosis_root) {
$keydir = "$gitosis_root/keydir";
$configuration_file = "$gitosis_root/gitosis.conf";
if (!file_exists($configuration_file)) {
print("Not found: $configuration_file\n");
return false;
}
$viewer = id(new PhabricatorUser())
->loadOneWhere('username = %s', 'sergey');
$old_configuration = parse_ini_file(
$configuration_file, true, INI_SCANNER_RAW);
$new_configuration = getCleanOldConfiguration(
$old_configuration);
// Get "system" keys to re-use if phab account uses the
// same public key
$system_keys = getSystemPublicKeys($keydir);
// Get list of all repos which is awailable for read
$all_repositories = array();
foreach ($old_configuration as $group => $values) {
if (startswith($group, 'repo')) {
$repository_name = substr($group, 5, strlen($group) - 5);
if ($repository_name == 'gitosis-admin')
continue;
$all_repositories[] = $repository_name;
}
}
// Fill in new configuration and keys
$used_keys = array();
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->execute();
foreach ($repositories as $repository_id => $repository) {
$type = $repository->getVersionControlSystem();
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
handleSingleRepository(
$keydir, $viewer, $repository, $all_repositories, $system_keys,
$new_configuration, $used_keys);
}
}
write_ini_file($new_configuration, $configuration_file);
removeUnusedPublicKeys($keydir, $used_keys);
return true;
}
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) {
$gitx_ssh = realpath(dirname(__FILE__) . "/gitx-ssh");
$abs_key = realpath($key);
$git = "SSH_KEYFILE=$abs_key GIT_SSH=$gitx_ssh ";
$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/gitosis-admin /path/to/id_rsa.pub\n");
exit(1);
}
$gitosis_root = $argv[1];
$key = $argv[2];
if (!repositoryPull($gitosis_root, $key)) {
print("Failed to pull changes from server.\n");
exit(1);
}
if (!rebuildConfiguration($gitosis_root)) {
exit(1);
}
if (!repositoryCommitAll(
$gitosis_root, 'Rebuild Gitadmin <null@git.blender.org>',
'Update to correspond changes in Phabricator')) {
print("Failed to commit changes.\n");
exit(1);
}
runGitSshCommand($gitosis_root, $key, 'push origin master');
?>