1. Что сделал?
Полная миграция данных. Переехал с
GitLabна свой сервер с идеей перенести сразу все (или выбранные) репозитории, при этом не потерять настройки, описания, картинки и мердж/пулл-реквесты. Решений по полной миграции я не нашел. Спойлер: закодил свой Python скрипт.Синхронизация с remotes. Настроил простой
git fetch/pushодновременно на свой и на всеGitLab,GitHub,...remotes. Спойлер: решение оформляю в другую статью.
2. Зачем?
Локальный (Self-hosted) сервис независим от РКН, КВН, "чебурнета" и гео-блокировки аккаунта.
Приватные репозитории на облачных хостингах могут утечь в интернет по независящим от вас причинам.
Локальные файлы хостинга можно бэкапить со всеми мердж-реквестами, настройками, описаниями и картинками.
3. Выбор локального хостинга
И тут конечно же не грех спросить ИИ. После удаления лишней воды от ИИ, получил сухой остаток в виде следующей таблицы.
Система | Ресурсы (min) | CI/CD | Лицензия | Краткий вывод |
|---|---|---|---|---|
GitHub Server | Платная коммерческая | Для корпоратов | ||
GitLab (CE) | 4+ vCPU, 8-16 ГБ RAM | Встроенный мощный CI/CD | Open Core (MIT+) | Для корпоратов с мощным железом |
Gitea | 1 vCPU, 512 МБ RAM | Gitea Actions (как GitHub) | MIT (Commercial) | Золотая середина. Очень похож на GitHub, идеален для команд. |
Forgejo | 1 vCPU, 512 МБ RAM | Forgejo Actions | GPL-3.0 (Open Source) | Полная независимость от корпораций и свобода. |
Gogs | 1 vCPU, 256 МБ RAM | Отсутствует (внешние) | MIT | Супер легкий. Старые компы/Raspberry. |
Я выбрал Gogs как самый легковесный сервис, т.к. как мне не нужны сторонние CI/CD.
4. Настройка Gogs
Скачать с https://gogs.io и установить (кроссплатформенно, можно в докер). Настройка не сложная, но есть нюансы (например по умолчанию протокол http вместо https). Расписывать в статье не стал.
А вы не забыли настроить свой хостинг перед миграцией?
5. Как?
У меня несколько десятков pet-проектов на Gitlab. Я пробовал варианты миграции через:
WEB интерфейсКазалось бы просто, все можно сделать через веб-интерфейс ? Но если побороть "лень" и с каждым репозиторием откликать мышкой в web интерфейсе. Недостатки встроенных средств миграции через webКаждый проект - только индивидуально (а хочется все сразу).
Gitlabограничен только экспортом в другойGitlabпроект (т.е. экспорта по сути нет).
Прим: если выбрать миграцию через web интерфейс в
Gogs"Тип миграции(Migration Type)"с установленной галкой"Зеркало(Mirror)", то Gogs будет периодически подключаться и подтягивать новые изменения сGitLab. Но этоread only, т.е. нельзяgit pushвGogs.
А при обычной миграции (галка снята), после копирования связь с
GitLabразрывается.После миграции через веб-интерфейс сразу бросилось в глаза, что логотип(аватар) не мигрировал. Данные, относящиеся к
git, перенеслись. Похоже веб-миграция ограничена под капотом простыми запусками командgit.
Вариант миграции из
Gitlabгрупп (не пользователя) скорее всего пройдет. А вот с миграцией вGogsорганизацию (не пользователя) возможно будут проблемы с правами, проверять не стал.
CLI bashнаLinuxЕсли просто
git clone/push --mirrorв bash, то результат все равно будет не удовлетворительным. Командыgitпереносят толькоисторию коммитов,веткиитеги.Поэтому в bash нужно добавлять обращение к
APIплатформы. Но это путь копипасты длиннющих заклинанийcurl,jq,grep,cut,xargs,gitпо несколько раз и с многократным исправлением ошибок.А на
Windowsсbatбудет еще сложнее.
Гуглил похожие решения, подходящих не нашел. Но есть примеры (со своими особенностями):
Опыт миграции из Gitea в GitLab. Сложно, но успешно
Быстрый бэкап всех ваших репозиториев Github
И что делать? Написал скрипт на питоне. Это проще, универсальнее и более функционально.
6. Перед миграцией
Создать
PAT токендляGitlabи дляGogs. С какого-то времени для авторизации вместо пароля используетсяPAT(Personal Access Token, тот самый "пароль", который для авторизацииgitпо протоколуhttps).
Прим: неProjectAccess Token(в настройках), а именнопроектаPersonalAccess Token(в настройкахпользователя)
Использование PAT необходимо, так как скрипт взаимодействует с API и выполняет клонирование по HTTPS.
💡 Совет: Сохраните токены в надежном месте, так как GitLab и Gogs показывают их только один раз при создании.API_TOKEN
Gitlab: (API_TOKEN) Нужно создатьPAT, можно по ссылке создать PATGogs: (USER_API_TOKEN) Нужно создатьManage Personal Access / Create New Token, можно по ссылке создать PAT.
Заменить значения в
settings.ini(UTF8) дляGitLabиGogsна свои`GitLab` и `Gogs` in settings.ini
[SRC_GITLAB_COM] секция. Заменить на свои значения:
# --- Настройки import from (GitLab) src --- [SRC_GITLAB_COM] URL = https://gitlab.com API_TOKEN = gitlab_api_my_PAT_token USER_ID = gitlab_my_idAPI_TOKEN= Gitlab_PATUSER_ID= ВUser Settings / Edit Profile /искать USER_ID)
[DST_GOGS_LOCAL] секция. Заменить на свои значен��я:
# --- Настройки export to (Gogs) dst --- [DST_GOGS_LOCAL] URL = https://gogs:3000 USER_NAME = gogs_my_name USER_PWD = gogs_my_pwd USER_API_TOKEN = gogs_api_my_PAT_token BASE_PATH = c:\Programs\Gogs DESCR_MAX_LEN = 256USER_NAME= Gogs_ваш_user_name_loginUSER_PWD= Gogs_ваш_user_passwordUSER_API_TOKEN= Gogs_PATBASE_PATH= куда/проинсталлирован/gogs
Вы в
GogsкакUSER_NAMEдолжны обладать админ-правами. Я не стал усложнять скрипт через создание репозиториев пользователя (не админа) с передачей прав от админа, а просто пользуюсь админскими правами пользователя.У вас должен быть установлен относительно свежий python v3.
Если при запуске скрипта будет ругаться на отсутствие библиотек, установите.
7. Миграция
CLI-Интерфейс (запуск и использование).
Проверка
GitLabдоступа, с выводом списка всех репозиториев:m:\>python migrate_repos.py --settings=path/to/my_settings.iniЗапуск миграции (Если все ок):
m:\>python migrate_repos.py --settings=path/to/my_settings.ini --runПо умолчанию ищется
settings.iniв той же папке, где и скрипт:m:\>python migrate_repos.py --runПосле выполнения скрипта выводится статус-репорт по всем репозиториям.
Доп. параметры в
settings.ini.В следующий раз, если что то поменялось в
Gitlab, можно форсировать обновлениеGogs(иначе скрипт игнорирует уже существующее вGogsрепо):FORCE_UPDATE_EXISTING_REPO in settings.ini
[COMMON] FORCE_UPDATE_EXISTING_REPO = False # False: Скрипт не изменит репо в DST, если был найден до создания (безопасный режим). # True: Скрипт будет обновлять (пушить) в DST-репозиторий, даже если он уже существует.Надеюсь описания (
description) репозиториев в 'Gitlab' у вас небольшие (до 256 символов). Иначе либо обрежет, либо миграция остановится:STOP_ON_REPO_DESCRIPTION_LIMITS in settings.ini
[COMMON] STOP_ON_REPO_DESCRIPTION_LIMITS = False # Gogs, Gitea has limitations for the description migration: # from web: Описание репозитория. Максимальная длина 512 символов # Но при попытке залить больше 256 выдает ошибку # Якобы нельзя переносы строк (no '\r' '\n' '\t'), но у меня получилось
Доп. параметры в коде скрипта.
Локальные временные репозитории создаются в папке TMP_DIR (создается папка "TmpLocalMigrateRepos" в пользовательской "TEMP" папке)
Добавил в код режим пропуска репозиториев и/или путей в URL.
SKIP_*
# SKIP (пропустить, если есть в списках) SKIP_REPOS = ["SkipRepo1", "SkipRepo2", "test", "qQq"] SKIP_PATHS = ["Group1/Subgroup1", "Another2/path2/to2"]Еще режим для дебага только перечисленных репо или путей:
DEBUG_*
# DEBUG (если список не пуст, пропускать всё, что В НЕГО НЕ ВХОДИТ) DEBUG_REPOS = [] # Если не пусто, будут обрабатываться ТОЛЬКО эти репозитории DEBUG_PATHS = [] # Если не пусто, будут обрабатываться ТОЛЬКО эти путиТокены в логе обфусцируются в
obfuscate_url()Скрипт честно ищет
gogscustom/conf/api.iniконфиг и параметры из него, иначе пробует дефолтные.При выполнении команды
push, 'gc' на больших репозиториях,gitсначала делает вид, что завис, а потом в лог вываливает кучу строк. Я не победил (в ко��е несколько вариантовrun_command). Возможно особенность выполнения гита в питоне под Windows.Форкнул https://gitlab.com/gitlab-org/gitlab-foss (похоже один из самых больших репо: 1.7 Gbytes и куча файлов, 116,627 commits и 4,138 branches). Запустил скрипт. Через несколько часов все появилось в
gogs. В коде есть проверка репо на монструозность. И в случае чего, запускаетсяgit gc --auto(Возможно не нужно, но сделал) передpush.
8. О скрипте
Скрипт переносит репозитории с использованием
gitс параметрами, полученными из запросов черезGitlab APIиGogs API* Все текущие репозитории одного пользователя. Скрипт в настоящее время не поддерживает работу с несколькими пользователями одновременно.branches,tags.Описание (с ограничением
gogsв 256 символов)Лого-картинка (не сразу научился брать с
GitLab). Прим: черезGogs APIтак и не получилось, поэтому сделал через запись в локальную БД (SQLite). Если у вас другой тип БД или вы скрипт запускаете не на хостинговой машине -нужно беспощадно вырезать соответствующий код в скрипте.Обновил скрипт: если скрипт запущен на той же машине, что и Gogs, он обновит аватары напрямую в SQLite, в противном случае обращение к БД (перенос аватаров)будет безопасно пропускаться.
Скрипт не переносит функциональность
Merge/Pull Requestsиз Gitlab в Gogs (сложно, можно сделать, но руки не дошли).Repo path:
В
gitlabпуть к репозиторию может быть с указанием в пути либо имени пользователя:https://gitlab.com/User1/Repo1.gitТо же самое и в
gogs:https://gogs:3000/User1/Repo1.gitЛибо путь репо (В
gitlab) может быть в группах (вместо пользователя):https://gitlab.com/Group1/Subgroup2/.../Repo2.gitВ
gogsаналогией пути с группами выступает путь к организации:https://gogs:3000/Org1/Repo1.gitПоэтому в скрипте меняю путьGroup1/Subgroup2/.../(до имени репозитория) на имя организацииOrg1/. Если она не существует, попутно создаю.
9. Code
Ссылка на код в Gitlab
Классы
ServiceManagerиSrc,Dstпредставляют основной функционалServiceManagerуправляет взаимодействиями для миграции репо (изSrcвDst)ServiceManager.init()читает конфигурациюsettings.iniсохраняя вSrc,DstServiceManager.processing()скачивает
jsonизGitlabc информацией о репозиторияхSrc.repos_json()В цикле запускает обработку каждого репозитория:
сохраняет из
jsonинфу о текущем репозитории вSrc.init()иDst.init()ServiceManager.mirror_repo()миграция 1 репыServiceManager.statusотчет о всех репах после миграции как результат обработки каждого, например:пример вывода статусов (заменил часть имен на пробелы чтобы не догадались ))
local |org |org ava|rep upd|rep ava|path/name fetched|user's |user's |forced |absent |AndrB /JapanCrossword xxxxxxx|xxxxxxx|xxxxxxx|xxxxxxx|xxxxxxx|[SKIPPED]AndrB /JapanCrossword2 fetched|user's |user's |forced |updated|AndrB /MigrateRepos fetched|user's |user's |forced |updated|AndrB /Prograstegy fetched|user's |user's |forced |updated|AndrB /RamCarDriver fetched|user's |user's |forced |updated|AndrB /RussianAICup2020CodeCraft fetched|user's |user's |forced |updated|AndrB /habr.com fetched|user's |user's |forced |updated|AndrB /japancrossword11plus fetched|user's |user's |forced |updated|AndrB /test fetched|user's |user's |forced |updated|AndrB /test2 fetched|user's |user's |forced |absent |AndrB /tgbot ... xxxxxxx|xxxxxxx|xxxxxxx|xxxxxxx|xxxxxxx|[SKIPPED]Andrey ovWork/Thinkcell.com_test .... fetched|alr xst|updated|forced |absent |Panaso FactorySupport/Orange_Box fetched|alr xst|updated|forced |absent |Panaso FactorySupport/Orange_Web fetched|alr xst|updated|forced |absent |Panaso FactorySupport/Orange_Web_GUI fetched|alr xst|updated|forced |absent |Panaso FactorySupport/Orange_Web_ova fetched|alr xst|updated|forced |absent |Panaso FactorySupport/Pasa_BPM_API fetched|alr xst|updated|forced |updated|Panaso FactorySupport/Pasa_eMMC_EmptyAreasSplitter fetched|alr xst|updated|forced |absent |Panaso FactorySupport/CAN_Sock_Raw_API ... fetched|alr xst|updated|forced |updated|QQQ77AaaBbbCcc/qqQ ... fetched|alr xst|updated|forced |updated|ses38 ocal/AdminSharedFoldersGroupsUsersRights fetched|alr xst|updated|forced |updated|ses38 ocal/InsiderEngine fetched|alr xst|updated|forced |updated|ses380Local/InsiderInfo fetched|alr xst|updated|forced |updated|ses380Public/MailSpamAssassin fetched|alr xst|updated|forced |updated|ses380Public/WwwEngine fetched|alr xst|updated|forced |updated|ses380Public/WwwInfo
В описании ниже есть зависимости действий от флага
FORCE_UPDATE_EXISTING_REPO, но пропущено для упрощения (в коде есть)ServiceManager.mirror_repo()магия из 3 составляющих:print(f"▄▄▄▄▄▄▄ §1. Клонирование (зеркальное)/Синхронизация из Src в локальное") prepare_local_mirror(src_authenticated_url, local_repo_path) print(f"▄▄▄▄▄▄▄ §2. Проверка Dst репо {repo_name} и Инициализация только если не существует") is_dst_repo_just_inited_as_new = Dst.init_repo() print(f"▄▄▄▄▄▄▄ §3. Пуш (зеркальный) локального в Dst") if is_dst_repo_just_inited_as_new : Dst.push(local_repo_path)
10. Комментарии на некоторые части кода
api_*url()набор служебных функций, возвращающихURLдляrequestкAPIдля разных случаев. Небольшое отличие дляGitlabи дляGogsменя немного удивили. Поэтому сделал универсальный подход:def req(method, url, **kwargs): # Unified requester. Usage: # req("GET", url) # req("POST", url, json=data) # req("POST", url, files=files_dict) # req("PATCH", url, json=data) return execute_request(method, url, Dst.api_headers(), **kwargs)init_repo()Если
is_orgи при проверкеstatus_code == 404, то создаю организацию с именем групп изGitlabрепо (см. выше почему)Проверка репо на
status_code == 200. Если не существует, создаю
wait_for_ready()- ждет статуса после выполненияrequest. Понадобилась после ошибок при быстром следующем обращении кAPI-push()c вызовомgit push --mirror --force, при этом дополнительно--follow-tagsдляtagsне нужен.Картинки репозиториев и групп , называются
avatars. ЧерезGogs APIзаливать картинки не получилось, пришлось костылить напрямую с запросом вGogsБД и с сохранением соответствующих репозиториям и организациям имен файлов и папок вGogs. Комментарии в коде.Gitlab APIпо взятию картинок обещал путь из первоначальногоjson, но по факту сработало только через обработку requestlog()нужен только для дебага. В процессе работы скрипта ожидаемые логические ошибки отлавливаются и такой print не включаетlog()log()
print(f"{log()}Bla bla {counter} bla bla") # line 649 in func mirror_repo()Пример вывода вызванных функций и вывод сообщения из этой строки во время дебага
migrate_repos.py <module> :696 migrate_repos.py processing :628 migrate_repos.py mirror_repo :649 Bla bla 42 bla bla
√ 🚀 Миграция выполнена.
Ниже то, что не сделано
A. Поддержка Merge Request при миграции.
Команды git (такие как git clone --mirror или git push) переносят только историю коммитов, ветки и теги.
Merge Request это функциональность хостинга (метаданные самой платформы), и не являются частью функциональности git (точно так же как описание и картинки на хостинге репозитория). Поэтому командами git они не переносятся. Мои проекты приватные, поэтому я это не реализовал.
B. API для различных типов хостингов.
Хотя API хостингов в чем-то похожи, но понятно, что они разные, поэтому в статье реализация только Gitlab -> Gogs.
GithubBitbucketCodebergGitFlicБуду думать...
√ 🔄 Вторая часть идеи.
Удобно делать простой git fetch/push сразу во все репозитории, когда один будет ведущим, остальные зеркалами
Настройка Git для одновременного push/fetch в несколько remotes.
Одной командой со всеми Gitlab, Github, ... и Gogs хостингами при выполнении git fetch/push.
Для конфигурирования нужно выполнить команды git в CLI, находясь в папке с клонированным репозиторием.
Либо (альтернативно) отредактировать .git/config, что быстрее, но легко допустить ошибки при редактировании.
В итоге получилось много команд и примечаний, конфигураций и примеров.
Решение оформляю в отдельную статью howto.
Прим: Это pet проект, c идеей попробовать в Python и получить just a Proof-Of-Concept, с целью обмануть свою лень и выполнить миграцию.
PS: Никаких рекламных ссылок на мой , тем более что его нет.Телеграм-канал
