Подмодули и поддеревья
Иногда проекту нужно включить в себя другой Git-репозиторий — общую библиотеку, набор конфигураций или вендорную зависимость. Git предлагает два подхода для этого: подмодули и поддеревья.
Git-подмодули
Подмодуль — это ссылка на конкретный коммит в другом репозитории. Родительский репозиторий не хранит код подмодуля напрямую — он хранит указатель.
Добавление подмодуля
git submodule add https://github.com/example/library.git libs/library
Это создаёт:
- Директорию
libs/library/со склонированным репозиторием - Файл
.gitmodules, записывающий URL и путь - Запись в индексе, указывающую на конкретный коммит
Клонирование репозитория с подмодулями
Когда кто-то клонирует ваш репозиторий, директории подмодулей будут пустыми. Им нужно выполнить:
git submodule update --init --recursive
Или клонировать с подмодулями за один шаг:
git clone --recurse-submodules https://github.com/example/project.git
Обновление подмодуля
cd libs/library
git pull origin main
cd ../..
git add libs/library
git commit -m "Update library submodule"
Типичные ошибки
- Забыли инициализировать — в свежесклонированных репозиториях директории подмодулей пустые
- Отсоединённый HEAD — подмодули извлекают конкретный коммит, а не ветку; вы всегда находитесь в состоянии отсоединённого HEAD внутри подмодуля
- Рассинхронизированные указатели — если вы обновите подмодуль, но забудете закоммитить родительский репозиторий, коллеги не увидят обновление
- CI/CD — системы сборки должны быть настроены на инициализацию подмодулей
Git Subtree
Поддерево объединяет код другого репозитория непосредственно в ваш репозиторий. Нет указателя — файлы фактически являются частью вашего проекта.
Добавление поддерева
git subtree add --prefix=libs/library https://github.com/example/library.git main --squash
Флаг --squash сжимает историю библиотеки в один коммит.
Обновление поддерева
git subtree pull --prefix=libs/library https://github.com/example/library.git main --squash
Отправка изменений обратно
Если вы изменили код поддерева и хотите отправить его в исходный репозиторий:
git subtree push --prefix=libs/library https://github.com/example/library.git main
Подмодуль и поддерево
| Аспект | Подмодуль | Поддерево |
|---|---|---|
| Хранение | Указатель на внешний коммит | Файлы объединены в репозиторий |
| Клонирование | Требуется --recurse-submodules | Работает из коробки |
| Настройка для коллег | Нужно выполнить submodule update --init | Ничего дополнительного |
| История | Отдельная история репозитория | Объединена с историей проекта |
| Обновление из источника | Pull внутри подмодуля, коммит родителя | git subtree pull |
| Сложность | Выше — дополнительные команды, легко ошибиться | Ниже — стандартные операции Git |
Когда использовать каждый подход
Используйте подмодули, когда:
- Зависимость большая и вы не хотите включать её в свою историю
- Вам нужно зафиксировать точную версию зависимости
- Подмодуль активно разрабатывается и имеет собственный цикл релизов
Используйте поддеревья, когда:
- Простота важнее разделения
- Коллеги не должны учить дополнительные команды
- Вы хотите самодостаточный репозиторий, работающий без доступа к интернету
Ключевые моменты
- Подмодули хранят указатель на внешний репозиторий; поддеревья объединяют код напрямую
- Подмодули требуют дополнительных шагов настройки (
--init,--recurse-submodules) - Поддеревья проще для коллег, но смешивают истории
- Оба подхода решают задачу встраивания одного репозитория внутрь другого