Прим: Дополняет мою статью "Скрипт полной миграции из GitLab на свой сервер".
1. После успешной миграции
А что делать со старым репозиторием?
Было бы логично использовать GitLab и другие хостинги в качестве вторичных (запасных).
Но как это делать? И еще не вручную?
Для синхрон��зации с remotes одновременно на свой Gogs и на все GitLab, GitHub, Bitbucket, ... remotes, об этом и других "хаках" ниже:

TL;DR Отредактировать свой конфиг:
Добавить соответствующие [remote ] секции, в каждой прописать url, fetch и несколько pushurl.
Опционально - прописать скрипт и использовать флаг для пуша.
Итого: набор команд для Использования
Начну из далека, но цель близка.
1.1 Нужен clone (если еще нет локального репо, иначе к сл. пункту)
Клонируем local repo (репозиторий) с remote (удаленный).
В качестве remote выбираем свой https://gogs:3000 сервер с репозиторием test.git
m:\Projects>git clone https://gogs:3000/User0/test.git
1.2 Переходим в папку локального репозитория
m:\Projects>cd test
Прим: Все команды запускаются в папке проекта
m:\Projects\test. Поэтомуm:\Projects>не указываю.
1.3 Определимся с тремя понятиями:
Remote (удаленный): сервер.
Remote-tracking branch (удаленно-отслеживаемая ветка): это локальная служебная копия («кэш») состояния удаленного сервера (например, origin/main). Это не рабочая ветка для кода, это указатель на последнее известное состояние сервера. И команды гита fetch или pull его обновляют ("кэшируют").
Local branch (локальная ветка): Моя рабочая ветка (просто main, а не origin/main), тут я пишу код.
2. Подготовка и настройка Remotes
2.1 Файл .git\config
В папке проекта есть файл m:\Projects\test\.git\config
и он может выглядеть примерно так:
[remote "origin"] url = https://gogs:3000/User1/test.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "main"] remote = origin merge = refs/heads/main [branch "brA"] remote = origin merge = refs/heads/brA [branch "brB"] remote = origin merge = refs/heads/brB
В секции [remote "origin"] виден удаленный адрес для origin(первоисточник).
Прим: При клонировании автоматически создается стандартный алиаз
origin. В примере указывает наhttps://gogs:3000.originиспользуется в командахgit, напримерgit push origin mainилиgit pull origin
В секциях[branch "xxxxxx"]видны настройки существующих бранчей (branchили ветка)
remote: указывает ветке в какой репозиторийremote«смотреть» (например, Git0 или origin).merge: указывает ветке какая именно ветка на удаленном сервере является «паро��» для локальной. Под терминомupstreamпонимают совокупность, например для локальной ветки[branch "brA"]:Куда идти?
remote = originКакую ветку там брать?
merge = refs/heads/brA. (Не обязательно локальная[branch "brA"]имеет такое же имяbrAвmerge = refs/heads/brA). Говорят "У ветки brA установлен upstream origin/brA".
Прим: Можно редактировать .git/config, и не использовать команды в
CLI.gitперечитывает файлы конфигурации (системный, глобальный и локальный) каждый раз при запуске любой команды. Если внесена ошибка в конфиг, то с помощью командыgit configможно проверить синтаксис.
Мне проще редактировать конфиг, чем использовать команды.
2.2 Remote origin, давай досвидания.
Давайте посмотрим куда указывает origin:
>git remote -v origin https://gogs:3000/User0/test.git (fetch) origin https://gogs:3000/User0/test.git (push)
Переименовываю origin в Git0 для наглядности и абстракции. Т.к. у нас будет несколько remote, любой из них может выступать в роли origin.
>git remote rename origin Git0 >git remote -v Git0 https://gogs:3000/User0/test.git (fetch) Git0 https://gogs:3000/User0/test.git (push)
Соответсвенно в файл test/.git/config добавятся:
[remote "Git0"] url = https://gogs:3000/User1/test.git fetch = +refs/heads/*:refs/remotes/Git0/* [branch "main"] remote = Git0 merge = refs/heads/main [branch "brA"] remote = Git0 merge = refs/heads/brA [branch "brB"] remote = Git0 merge = refs/heads/brB
2.3 Алиазы вместо длинных URL
Сразу добавляю URL алиазы для дальнейшего удобства использования.
>git config --local url."https://Gogs:3000/User0". insteadOf "url0" >git config --local url."https://Gitlab.com/User1".insteadOf "url1" >git config --local url."https://Github.com/User2".insteadOf "url2" >git config --local url."https://xxxxxx.com/User3".insteadOf "url3"
файл test/.git/config:
[url "https://Gogs:3000/User0"] insteadOf = url0 [url "https://Gitlab.com/User1"] insteadOf = url1 [url "https://Github.com/User2"] insteadOf = url2 [url "https://xxxxxx.com/User3"] insteadOf = url3
2.4 Секция [remote "origin"]
Забегая вперед, важное замечание. Обычно конфиг содержит одну [remote "origin"] секцию. Выглядит примерно так:
[remote "origin"] url = https://hosting.com/user1/test1.git fetch = +refs/heads/*:refs/remotes/origin/* pushurl = https://hosting.com/user1/test1.git
url =может быть только один (используется дляfetchи дляpush, если не заданpushurl).fetch =может быть несколько (они определяют правила сопоставления веток, git позволяет скачивать данные из разных групп веток одного репозитория).
Прим: в теории, если нет строки
fetch =, то командаgit fetch имя(без указания веток) не будет знать, какие ветки скачивать и куда их сохранять локально. Она просто ничего не обновит в вашихrefs/remotes/. Каждый раз придется указыватьgit fetch Git0 main:refs/remotes/Git0/main
pushurl =опционально и обычно отсутствует, т.к. совпадает сurl. Прим: безpushurl, git использует основнойurlкак для скачивания (fetch), так и для отправки (push). Может быть несколько строкpushurl(в таком случае,gitотправит данные во все указанные адреса одновременно при выполненииgit push).
Прим: git пушит в указанном порядке и если возникнет любая ошибка (например сервер лежит), то git "не парится" по поводу оставшихся в списке
2.5 Команды remote set-url ***
Для обновления конфига нужны команды:
: обновить fetch-URL. Прим: Если нет отдельной строки pushurl, эта команда обновит адрес и для fetch, и для push одновременно. >git remote set-url Git0 url0/test.git : обновить push-URL. >git remote set-url --push Git0 url0/test.git : добавить еще (+1) push-URL. >git remote set-url --add --push Git0 url0/test.git : удалить push-URL >git remote set-url --delete --push Git0 url0/test.git
3. Обновление конфига для зеркалирования ВСЕХ репозиториев
3.1 Для синхронного git fetch и git push для всех репозиториев, я сделал такой конфиг:
[remote "Git0"] url = url0/test.git fetch = +refs/heads/*:refs/remotes/Git0/* # fetch = +refs/heads/*:refs/remotes/DONT_SET_ANY_OTHERS/* pushurl = url0/test.git # ONLY HERE THE ORIGIN OUR Git0 pushurl = url1/test.git pushurl = url2/test.git pushurl = url3/test.git [remote "Git1"] url = url1/test.git fetch = +refs/heads/*:refs/remotes/Git1/* # fetch = +refs/heads/*:refs/remotes/DONT_SET_ANY_OTHERS/* pushurl = url1/test.git pushurl = url2/test.git pushurl = url3/test.git [remote "Git2"] url = url2/test.git fetch = +refs/heads/*:refs/remotes/Git2/* # fetch = +refs/heads/*:refs/remotes/DONT_SET_ANY_OTHERS/* pushurl = url2/test.git pushurl = url1/test.git pushurl = url3/test.git [remote "Git3"] url = url3/test.git fetch = +refs/heads/*:refs/remotes/Git3/* # fetch = +refs/heads/*:refs/remotes/DONT_SET_ANY_OTHERS/* pushurl = url3/test.git pushurl = url1/test.git pushurl = url2/test.git
Нужно оставить remote (удаленные) хостинги только реально используемые,
т.к. несуществующие будут мешать остальным, такие строки нужно или удалить или закомментирвоать.
Тут и далее для примера выбрал кол-во 4 (а можно еще Bitbucket, Codeberg, GitFlic, ...).
3.2 Основной remote
Обращаю внимание, основным remote м.б. выбран любой remote. Я выбрал Git0 (бывший origin). Поэтому именно для него (в секции [remote "Git0"] ) и только для него есть pushurl (в url0)
[remote "Git0"] pushurl = url0/test.git # only here the origin our Git0
4. Теория и тестирование.
4.1 Флаг --all в fetch, pull, push только пишется одинаково
Важно различать, что именно обозначает флаг --all в разных командах:
1)git fetch --all - репозитории,
2)git pull --all - репозитории, но только для текущей ветки, есть нюансы! (см. ниже)
3)git push --all - ветки
Прим:
pullиpushпо умолчанию работают с однимorigin.
1)git fetch --all:
Обновляет информацию (коллеги в команде что-то уже запушили) обо ВСЕХ ветках (remote-tracking branches) из ВСЕХ удаленных репозиториев (Git0, Git1, Git2, Git3), позволяя заренее видеть чужие коммиты. «Облегчает жизнь новостями» о конфликтах в коде перед будущим мержом.
Локальные ветки НЕ меняются.
2)git pull --all: - это сокращение для git fetch --all && git merge, как я понимаю. Могу ошибаться, я не пользуюсь pull --all.
Обновляет информацию (как
fetch --all),ДРУГИЕ локальные ветки НЕ меняются.
В ТЕКУЩУЮ ветку пытается выполнить merge по очереди из репозиториев, где есть ветка с таким же именем.
pullвместе с--allредко используется, так как обычно ветка связана только с одним репозиторием).git pull --all- коварная команда. Игнорирует тот факт, что у ветки обычно только один «хозяин» (upstream).Пройдет по всем репозиториям и, если найдет там ветку с таким же названием (пусть даже с разным содержимым), попытается влить её в текущую.
Для синхронизации зеркал эта команда вредная. Именно поэтому безопаснее связкаfetch --all+mergeиз основного источника.Вопрос: Пусть текущая ветка
brA. Идет ли речь про мерж вbrA, читая секцию[branch "brA"]изremote = Git0, изGit0/brA?
Ответ: Нет. В этом и заключается «коварство». Если используется флаг--all, Git игнорирует строкуremote = Git0в секции[branch "brA"](игнорирует настройки конкретного upstream для этой ветки). Просто берется имя текущей веткиbrAи ищется во всех объявленных[remote "..."]Вопрос: "...из репозиториев, где есть ветка с таким же именем." Т.е. читается конфиг, ищутся все
[remote ..]секции, и на этих серверах ищется ветка изmerge = refs/heads/brA?
Ответ: Если кратко - да, идет цикл по этим серверам: Если там есть веткаbrA, то сделатьmerge сервер/brAв локальную.Но гугль пишет, в стандартном поведении Git чаще всего выполнит мерж из того удаленного репозитория, который первым вернул результат или является дефолтным.
3)git push --all:
Отправляет ВСЕ ветки (которые существуют локально) в один(!) удаленный репозиторий (по умолчанию origin). Т.е. это по сути
git push Git0 --all(гдеall- ветки)
4.2 Проверка: fetch и push с опцией --all
>git fetch --all & echo ======= & git push --all Fetching Git0 Fetching Git1 Fetching Git3 Fetching Git4 ======= Everything up-to-date Everything up-to-date Everything up-to-date Everything up-to-date
4.3 Тестирование (1): коммит и пуш для текущей ветки
Для теста сделал коммит и проверяю, как push проявится во всех репозиториях для текущей ветки:
>git fetch & echo ======= & git push ............ ======= Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 292 bytes | 292.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) To https://Gogs:3000/AndrBell/test.git aac4183..6a982fb brB -> brB Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 292 bytes | 292.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) remote: remote: To create a merge request for brB, visit: remote: https://gitlab.com/AndrBell/test/-/merge_requests/new?merge_request%5Bsource_branch%5D=comm remote: To https://Gitlab.com/AndrBell/test.git aac4183..6a982fb brB -> brB ............
Прим: Выше использовал команды без
--all. Это означает, что изменения запушатся только с текущей ветки.
4.4 Тестирование (2): коммит и пуш для всех веток
Для теста сделал коммиты в разные ветки и проверяю, как push проявится во всех репозиториях для всех веток:
>git fetch --all & echo ======= & git push --all ............ To https://Gogs:3000/AndrBell/test.git 2372260..0410db0 brA -> brA a086469..e7bfa98 brB -> brB ............ To https://Gitlab.com/AndrBell/test.git 2372260..0410db0 brA -> brA a086469..e7bfa98 brB -> brB ............
Прим:
git pull --all. Команда pull объединяет fetch и merge. Git не умеет делать merge (слияние) сразу изо всех репозиториев во все локальные ветки.git pull --allобновит только текущую ветку.
Решение: Вместоgit pull --allлучше использоватьgit fetch --allи затем делатьgit mergeиз основного источника (Git0).
Дляpushлучше всего будет, когдаGit0инициирует отправку на все свои pushurl
>git fetch --all & echo ======= & git push Git0 --all
Пример: отправить изменения в ТОЛЬКО текущей ветке во все pushurl, перечисленные для Git0
>git push Git0
Прим: Внимание!, если один хостинг из списка будет недоступен, то
gitзапушив изменения на предыдущие, не продолжит со следующего. А когда станет доступен,gitзальет изменения на оставшиеся
4.5 Итого: набор команд для использования
: Стандартно сфетчить все серверы (обновить локальные Remote-tracking branchs) перед пушем > git fetch --all : Пуш ТЕКУЩЕЙ ветки на все серверы (через прописанный в Git0 список pushurl) > git push Git0 : Пуш ВСЕХ веток ...--"--. Думаю, что такой вариант полной синхронизации (всех веток) нужен в редких случаях. > git push Git0 --all
5. Размышления о хорошем
5.1 Что такое хорошо и что такое плохо. (с) Маяковский
А чем хорошо: git push Git0 --all
и плохо просто: git push --all
git push Git0 --allпринудительно отправляет все локальные ветки из[remote "Git0"]по очереди на url0, url1, url2 и url3. Т.е. обновит все зеркала из[remote "Git0"].--git pushведет себя по-разному, т.к. не указано имя (allGit0).gitсмотрит на текущую ветку.В зависимости от настроек Git:
Если она отслеживает (upstream) репозиторий именно
Git0, то сработает так же. Ок.Но если текущая ветка настроена на отслеживание например
Git1, тоgit push --allотправит все ветки только в Git1 (и соотвественно по его списку pushurl).А вот если ветка ничего не отслеживает,
gitможет выдать ошибку или использовать репозиторий по умолчанию (origin), если он настроен. В моей конфигурации origin не указан, поэтомуgit, скорее всего, попросит уточнить. Не пробовал.
5.2 Еще лучше! Level Up
Добавил в конфиг .git/config вот такой скрипт:
[alias] push-all-gits = "!f() { for r in $(git remote | grep '^Git'); do echo \"Pushing to $r...\"; git push $r --all --quiet && echo \"[OK] $r\" || echo \"[FAIL] $r\"; done; }; f"
Или тоже самое добавить через консоль:
>git config --local alias.push-all-gits "!f() { for r in $(git remote | grep '^Git'); do echo \"Pushing to $r...\"; git push $r --all --quiet && echo \"[OK] $r\" || echo \"[FAIL] $r\"; done; }; f"
Скрипт выполняет шаги:
git remoteсоставляет список всех удаленных репозиториев.grep '^Git'фильтрует их, оставляя только те, названия которых начинаются на "Git". См. ниже примечание.for r in ...проходит циклом по каждому найденному репозиторию.git push $r --allотправляет все ваши ветки в этот репозиторий.echo "[OK]"или"[FAIL]"выводит результат операции для каждого сервера.
Напомню: Git останавливается при ошибке одного из pushurl. Скрипт push-all-gits решает проблему, обходя репозитории по отдельности. И тогда пуш пройдет на все живые сервера.
5.3 Стресс-тест скрипта (на время теста я отключил Git0):
>git push-all-gits fatal: unable to access 'https://Gogs:3000/AndrBell/test.git/': Failed to connect to Gogs port 3000 after 2082 ms: Could not connect to server [ERROR] Git0 Everything up-to-date [SUCCESS] Git1 Everything up-to-date [SUCCESS] Git2 Everything up-to-date [SUCCESS] Git3
Прим: несколько слов о
grep '^Git'
Это защита от случайного пуша, например: в upstream (чужой репозиторий, откуда только берется код) или в origin (если хочется обновлять только зеркала).
Если убрать| grep '^Git', будет цикл по всем доступным именам из git remote. Когда использовать без фильтрации? Если удаленные репозитории называются стандартно (origin, backup, mirror), т.е просто, одной командой обновить абсолютно все.
Прим: Для гиков:
--localфлаг указывает Git записать настройки только для текущего репозитория (в файл .git/config). Но можно же чтобы команда git push-all-gits работала везде на компьютере, тогда нужно использовать флаг --global. Как вариант с фильтром или безgrep '^Git', создать два алиазаpush-all-gitsиpush-all`.
5.4 Итого: команда для использования с push-all-gits
Прим: В конфиге добавлен скрипт
push-all-gits
: Одной "кнопкой" > git fetch --all && git push-all-gits
Прим: это от��азоустойчивый вариант: Если один сервер недоступен, остальные всё равно обновятся.
5.5 А если все плохо! Troubleshooting
Если в процессе экспериментов сломался .git/config, то где именно проверить можно:
>git config --list --local
5.6 Бонус, к теме не относится, но не сделайте плохо всем! (git push: спам тегами tags)
git push не отправляет теги (tags). Для этого есть флаг --tags.
git push <remote> --tags git push --tags
И все теги из локального репо (даже те, которые на других ветках) мгновенно окажутся в общем репозитории.
Правильно:
Либо использовать
git push origin v1.x.x- улетит только один конкретный тег. Это самый безопасный способ.Либо настроить
git config --global push.followTags true(начиная с версии 2.4). Теперь при обычномgit pushGit запушит новый тег, теги с других веток при этом не улетят.
PS: Никаких рекламных ссылок на мой , тем более что его нет.Телеграм-канал
