Источник: Nuances of Programming
Работая с клиентами на таких платформах, как Github или Gitlab, мы в Gitstart часто сталкиваемся с необходимостью синхронизировать кодовые базы между удаленными репозиториями. По каждой поставленной клиентом задаче наши разработчики работают внутри ветви частного репозитория (клона или форка клиентского репозитория), и необходима уверенность, что состояние клиентского репозитория соответствует нашему, как и наоборот. Со временем синхронизация репозиториев становится повторяющимся процессом, поэтому имеет смысл его как-то автоматизировать.
Встречайте Gitstart Fork: это наш внутренний инструмент, который использует мощь веб-хуков, чтобы синхронизировать код между парой репозиториев практически в реальном времени.
Но, спросите вы, как это работает? Чтобы упростить кодовую базу, мы решили разделить функциональность на две части:
- Pull: перемещение изменений из клиентского репозитория в наш репозиторий.
- Push: перемещение изменений из нашего репозитория в клиентский репозиторий.
В этой статье мы сосредоточимся на обсуждении Pull.
Стек технологий: большую часть кода мы пишем на TypeScript и Nodejs. В качестве базы данных мы выбрали PostgreSQL с Hasura в качестве движка GraphQL (там есть изящная функция под названием подписка, которая упрощает обработку).
Для простоты мы будем говорить о репозиториях Github, однако написанное можно распространить на любой удаленный сервис на основе git, к примеру Gitlab или Bitbucket.
1. Функции
Fork Pull предоставляет следующий функционал для синхронизации:
- Управление ветвями: укажите, какие именно ветви нужно синхронизировать.
- Детализированное управление синхронизацией файлов: укажите, какие именно папки/файлы будут синхронизированы, а какие — нет.
- Поддержка .gitignore: синхронизация не затронет файлы, перечисленные в .gitignore.
2. База данных
Всё начинается с таблицы, где отслеживаются оба синхронизируемых репозитория: какие ветви синхронизировать в этих репозиториях, а также любые файлы или папки, которые синхронизировать не нужно. Высокоуровневая схема будет выглядеть следующим образом:
Мы также ведем учет всех так называемых “извлечений” (pull), которые мы сделали:
Эта таблица служит промежуточным звеном между началом и окончанием pull’а. Мы в основном используем его для отслеживания хода извлечения. Так можно оценить успех или неудачу pull, узнать о произошедших ошибках, рассчитать необходимое для операции время и многое другое.
Успешный pull регистрируется в третьей таблице, которая отслеживает пару головных коммитов синхронизированных ветвей (важность этой таблицы станет ясна в следующем разделе):
Примечание: все схемы здесь даны исключительно для того, чтобы создать общее представление. Поля можно как включать, так и исключать, в зависимости от желаемой функциональности.
3. Отслеживание изменений
База данных — это отлично, но как мы определяем, когда нужно делать pull? В конце концов, это самая важная часть инструмента, который претендует на “работу в реальном времени”. Вот где проявляется магия веб-хуков и подписок Hasura! У нас в Gistart уже создана довольно надежная и масштабируемая инфраструктура веб-хуков. Вкратце ее можно резюмировать так:
- Все веб-хуки со сторонних сервисов, таких как Github или Jira, хранятся в нашей базе данных.
- Впоследствии эти веб-хуки обрабатываются и соответствующие таблицы обновляются соответствующим образом.
- Если обработка любой полезной нагрузки веб-хука не удается, мы ждем 5 минут, прежде чем повторить попытку.
Для наших целей нужно сосредоточиться только на одном типе событий Github: push. Это событие происходит всякий раз, когда один или несколько коммитов помещаются в ветку или тэг репозитория.
Это вебхук-событие мы объединяем с щепоткой магии подписок и получаем такую последовательность событий для обнаружения изменений:
- Коммиты передаются в наше хранилище исходного кода fromRepo.
- Веб-хук push регистрируется в базе данных и обрабатывается.
- Это изменяет головной коммит SHA-1 исходной ветви в нашей базе данных.
- Как только база данных обновляется, подписка (см. GraphQL-код ниже) замечает, что ветвь основного репозитория содержит новые коммиты, которые еще не были извлечены, и запускает в соответствии с этим обработчик:
- Затем код выполняет pull и репозитории синхронизируются (подробнее об этом в следующем разделе).
Основным триггером события pull служит обновление базы данных. В нашем случае это достигается с помощью веб-хуков благодаря их способности реагировать в реальном времени. Однако того же самого результата можно добиться и через ряд других вариантов.
Внимание: следующие шаги предполагают, что база данных актуализирована.
4. Архитектура Git
И здесь начинается настоящее веселье. На самом деле, выполнить pull не очень сложно. В качестве клиента git мы используем isomorphic-git. Операции git можно разделить на 5 этапов:
4.1 Клонирование (clone)
Репозитории мы клонируем во временные папки. Если такие папки уже существуют, мы просто извлекаем последние коммиты и переключаемся на интересующие нас ветви. Ни удаления папок, ни повторного клонирования каждый раз не происходит, потому что pull производится быстрее, чем клонирование.
4.2 Удаление (delete)
В целях синхронизации мы убираем и удаляем из индекса git все файлы/папки, присутствующие в toRepo, но не в fromRepo, следя за тем, чтобы исключить игнорируемые файлы/папки. Для этого мы применяем glob matching (глобальное сопоставление): определяем разницу между наборами путей к файлам и удаляем их:
4.3 Копирование (copy)
Теперь мы копируем все файлы из fromRepo в toRepo с помощью легкой библиотекиcopy-dir. Эта библиотека предоставляет фильтр для фильтрации путей к файлам, что в нашем случае полезно:
4.4 Добавление и коммит (Add & commit)
Простой шаг, на котором мы добавляем все пути к файлам в будущий коммит (стейджинг) и делаем коммит, получая SHA-1 головного коммита для нужд базы данных.
4.5 Отправление (push)
Наконец мы отправляем коммиты в наш toBranch. Этот push вызовет веб-хук, который мы впоследствии обработаем ради сохранения целостности нашей базы данных.
5. Заключение
И вот, репозитории наконец-то синхронизированы!
Это похоже на волшебство, но именно так оно и ощущается, когда технология используется изящно и с умом.
Спасибо за чтение!
Читайте также:
Перевод статьи: Navoneel Bera, “Syncing Git Repos in Real-Time: Part 1”