Паранойя никогда не бывает избыточной, особенно для меня. Когда я пишу код и вдруг теряю его по разным причинам, не сохранился, заснул на бекспейсе, диск с git репозиторием посыпался, я всегда расстраиваюсь и не хочу писать снова, то что уже было написано. Ниже будет описано как предохраниться от одной из этих проблем, а именно с потерей данных по средствам гибели HDD. Мы реализуем кросс пуш во все возможные хранилища для того чтоб уберечь наши труды от забвения.
Я использую GitLab CE для локального управления проектами, крутится он у меня на старом ноутбуке. К сожалению кросспостинг из GitLab доступен только в EE, а я очень жадный бедный разработчик и по этому решил эту проблему через hardcode в хуках (hooks) git хранилища.
Для начала нам нужно создать ключ для push в другие хранилища через ssh. Как это сделать, можно почитать в официальной документации к git. Если мы собираемся пушить в github или gitlab или bitbucket или другой репозиторий нам нужно добавить публичный ключ на каждый сервер в который мы хотим дублировать изменения в коде для пользователя через которого будем осуществлять push в файл ~/.shh/authorized_keys если это обычный сервер или в GUI gitlab/github/bitbucket. Еще важный момент для автоматического зеркалирования, нужно добавить все внешние хосты в файл ~/.ssh/known_hosts , это доверенные хосты, сохранить ключ для них нужно один раз. Я делал это в ручную.
После того как мы решили проблему с доступами к удаленным git хранилищам, можно приступать к конфигурации хуков в основном репозитории. git hooks хранятся в корне хранилища .git/custom_hooks. Нас интересует хук который отвечает за обработку git push в нашем репозитории, а имя его post-receive. Создадим этот файл в папке custom_hooks и добавим в него строчку с выполнением bash скрипта:
#!/bin/bash
git push --set-upstream example_remote_name --all
Важно! example_remote_name это имя удаленного хранилища которое добавлено через git remote add, если его не будет, то ничего не заработает:
git remote add example_remote_name [email protected]:bpteam/test_php.git
После внесения всех репозиториев в хук, нам нужно дать права на чтение и выполнение файла post-receive для пользователя под которым выполняется событие, обычно этого пользователя зовут git. По этому не забудем для файла post-receive выполнить команду chmod 700 post-receive && chown git post-receive.
Все теперь при пуше в репозиторий будет автоматически выполняться git push example_remote_name.
Это все круто если мы это сделали изначально, но у меня насобиралась большая куча репозиториев(over 30 штук) и для каждого хранилища проделывать эти телодвижения не хочется, да и накосячить можно. Для этого я написал скрипт на православном PHP, который сделает все сам из представленного списка репозиториев. В переменную $mirroring добавляется конфиг для генерации файлов для каждого репозитория. Все вышеописанные действия выполнятся автоматически
<?php
$eventDir = "/custom_hooks";
$eventFile = "/post-receive";
$gitlab = "/var/gitlab/data/git-data/repositories/%s.git"; // path to your repositories
$localdrive = "ssh://root@localdrive/volume1/git/%s"; //pattern for remote git storage
$github = "[email protected]:%s.git"; //pattern for github
$bitbucket = "[email protected]:%s.git"; // pattern for bitbucket
/**
* assoc array, where key is local git dir
* $mirroring[gitlab_repo] = [
* remote_name => repository_url,
* ]
* ];
*/
$mirroring = [
'bpteam/test_php' => [
'localdrive' => sprintf($localdrive, "test_php"),
'github' => sprintf($github, "bpteam/test_php"),
'bitbucket' => sprintf($bitbucket, "bpteam22/test_php"),
],
];
try {
foreach($mirroring as $repoName => $mirrors) {
$hooks = [];
cdToRepoDir(sprintf($gitlab, $repoName));
foreach ($mirrors as $mirrorName => $mirrorUrl) {
gitRemoteRemove($mirrorName);
gitRemoteAdd($mirrorName, $mirrorUrl);
$hooks[] = genHook($mirrorName);
}
GitlabCustomHooks(sprintf($gitlab, $repoName) . $eventDir);
makeHook(sprintf($gitlab, $repoName) . $eventDir . $eventFile, "#!/bin/bash\n" . implode("\n", $hooks));
}
} catch(Exception $e) {
echo $e->getMessage() . "\n";
}
function cdToRepoDir($dir)
{
if(!chdir($dir)) {
throw new Exception($dir . " is not exists");
}
}
function gitRemoteRemove($mirrorName)
{
$message = exec(sprintf("git remote remove %s", $mirrorName));
if($message && !preg_match('~Could not remove config section~i', $message)) {
throw new Exception($mirrorName . " can't remote remove : " . $message);
}
}
function gitRemoteAdd($mirrorName, $mirrorUrl)
{
$message = exec(sprintf("git remote add %s %s", $mirrorName, $mirrorUrl));
if($message) {
throw new Exception($mirrorName . " can't remote add: " . $message);
}
}
function genHook($mirrorName)
{
return sprintf("git push --set-upstream %s --all", $mirrorName);
}
function GitlabCustomHooks($dirName) {
if (!file_exists($dirName)) {
mkdir($dirName, 0777);
}
}
function makeHook($fileName, $command)
{
$fh = fopen($fileName, "w+");
fwrite($fh, $command);
fclose($fh);
$fileMode = 0777;
chmod($fileName, $fileMode);
$repoDir = dirname(dirname($fileName));
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($repoDir));
foreach($iterator as $item) {
if(!chmod($item, $fileMode)) {
var_dump($item);
echo PHP_EOL;
}
}
}