Паранойя никогда не бывает избыточной, особенно для меня. Когда я пишу код и вдруг теряю его по разным причинам, не сохранился, заснул на бекспейсе, диск с 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;
        }
    }
}