Сабмодули в Git: как внедрить их без сюрпризов
Сабмодуль помогает подключить один Git-репозиторий к другому, но родительский проект хранит не его файлы, а ссылку на конкретный коммит. Разбираем, как закрепить эту связь, настроить клонирование и обновлять зависимость так, чтобы сборка не менялась неожиданно.

Подключили общий репозиторий к проекту, а коллега клонировал основной код и получил пустую папку вместо зависимости? Это типичный сюрприз с сабмодулями: родительский репозиторий хранит ссылку на коммит другого репозитория, но при обычном клонировании не обязан загружать его содержимое. Ниже — практический сценарий для Git 2.40 и новее: как добавить сабмодуль, зафиксировать его состояние и не дать обновлению незаметно изменить сборку.
Сначала решите, нужен ли именно сабмодуль
Сабмодуль уместен, когда код должен оставаться отдельным репозиторием: у него свой владелец, история, права доступа или цикл выпуска. Родительский проект при этом может зафиксировать конкретную версию этого кода.
Если вам нужен готовый релиз библиотеки, сначала проверьте пакетный менеджер: версия зависимости там обычно видна в манифесте и lock-файле. Если исходники нужно включить в родительский репозиторий и развивать вместе с ним, сравните сабмодуль с Git subtree. Это не три названия одного решения: отличаются границы истории и то, что должен знать разработчик при клонировании.
| Вариант | Что попадает в родительский проект | Цена и когда выбирать |
|---|---|---|
| Сабмодуль | Ссылка на коммит отдельного репозитория | Нужно отдельно клонировать и обновлять зависимость. Подходит, когда важны самостоятельная история и точная фиксация коммита. |
| Git subtree | Файлы зависимости внутри дерева родительского репозитория | Клонирование проще, но обновления и синхронизация истории требуют отдельной дисциплины. Подходит, если потребителям не нужно работать с репозиторием зависимости напрямую. |
| Пакетный менеджер | Обычно ссылка на опубликованный пакет и его версию | Удобно для библиотеки с релизами. Нужны процесс публикации и совместимость пакета с проектом. |
Дальше рассматриваем сабмодуль. Инвалидация кешей, правила выпуска пакетов и подробный процесс работы с subtree — отдельные темы.
Что именно фиксирует Git
Сабмодуль — не просто вложенная папка. В индексе Git родительского проекта хранится gitlink1: идентификатор коммита дочернего репозитория. Поэтому два разработчика могут получить разные версии файлов, если каждый вручную переключил сабмодуль на свой коммит и не закоммитил изменение ссылки в родительском проекте.
Надёжность сабмодуля держится не на том, что папка выглядит заполненной, а на проверяемой ссылке родительского репозитория на конкретный коммит.
В корне родительского проекта также появляется файл .gitmodules. В нём Git хранит URL и путь сабмодуля. Сам указатель на его коммит хранится отдельно — в родительском индексе. Поэтому для полноценного изменения обычно нужны коммиты в обоих репозиториях: сначала в дочернем, затем в родительском.
Добавление и обычное клонирование
Команды ниже подходят для Git 2.40 и новее. Замените адрес примера на URL репозитория, к которому у команды есть доступ. Пусть зависимость должна лежать в vendor/shared-ui.
git submodule add https://HOST/TEAM/REPOSITORY.git vendor/shared-ui
git status
git commit -m "Add shared-ui submodule"
После добавления проверьте, что Git показал новый файл .gitmodules и путь сабмодуля. Закоммитьте оба изменения в родительский репозиторий. Если дочерний репозиторий приватный, отдельно убедитесь, что у разработчиков и CI есть доступ к его URL: доступ к основному репозиторию его не гарантирует.
Новый рабочий клон создавайте с загрузкой сабмодулей сразу:
git clone --recurse-submodules https://HOST/TEAM/PARENT.git
Если проект уже клонирован обычным способом, заполните сабмодули отдельно:
git submodule update --init --recursive
Проверка: в каталоге зависимости есть файлы, а git submodule status --recursive показывает зафиксированные коммиты. Ключ --recursive важен, если у дочернего репозитория тоже есть сабмодули. Без него верхний уровень может загрузиться, а вложенная зависимость — остаться пустой.
Обновляйте ссылку намеренно
Обычный git submodule update приводит рабочее дерево сабмодуля к коммиту, зафиксированному родительским проектом. Это полезно при сборке: команда воспроизводит уже записанное состояние, а не ищет свежий код. Для осознанного перехода на более новый коммит используют --remote:
git submodule update --remote vendor/shared-ui
git status
git add vendor/shared-ui
git commit -m "Update shared-ui reference"
После обновления посмотрите, что именно изменилось, и зафиксируйте новый указатель в родительском репозитории. Флаг --remote получает актуальный коммит выбранной ветки; он не означает «обновить зависимость безопасно». Если команда ориентируется на движущуюся ветку, результат со временем изменится. Для более предсказуемого поведения настройте ветку в .gitmodules, а каждое обновление проверяйте и коммитьте как отдельное изменение. Даже тогда воспроизводимую сборку задаёт зафиксированный коммит, а не название ветки.
Если разработчику нужно менять саму зависимость, порядок такой: внести изменения и создать коммит внутри репозитория сабмодуля; затем перейти в родительский проект, проверить изменившуюся ссылку и закоммитить её. Незакоммиченные изменения в дочернем репозитории не становятся частью родительского коммита.
Что ломается на практике
- Папка пустая после клонирования. Клонировали родительский репозиторий без инициализации сабмодулей. Запустите
git submodule update --init --recursive. - У коллеги ошибка доступа. URL доступен только автору, который добавил зависимость, или настроен под его окружение. Проверьте
.gitmodules, права команды и доступ CI к дочернему репозиторию. - Сборка использует не тот код. Сабмодуль вручную переключили на другой коммит. Сверьте
git submodule statusс зафиксированным состоянием родительской ветки и верните его командойgit submodule update --init --recursive. - Обновление не видно в родительском diff. Изменения закоммитили в дочернем репозитории, но не закоммитили новый указатель в родительском. Проверьте
git statusиз корня основного проекта. - Переключение ветки оставляет неожиданный коммит. Разные ветки родительского проекта могут ссылаться на разные коммиты сабмодуля. После переключения ветки синхронизируйте его состояние командой
git submodule update --init --recursive.
Когда сабмодуль себя оправдывает
Берите сабмодуль, если команде нужна независимая история и родительский проект должен ссылаться на точный коммит. Выбирайте пакетный менеджер, если зависимость выпускается как библиотека с версиями. Рассмотрите subtree, если важнее получить все файлы обычным клонированием и не заставлять каждого разработчика отдельно инициализировать вложенные репозитории.
До внедрения проверьте не только локальную машину, но и CI: умеет ли он получать приватный дочерний репозиторий и выполняет ли рекурсивное обновление. Сравнивать здесь следует не скорость Git, а число обязательных действий и точек отказа в вашем процессе. Универсального замера времени для всех репозиториев нет.
Что делать дальше
Добавьте сабмодуль в тестовую ветку и проверьте полный цикл: новый клон, рекурсивное обновление, запуск сборки и обновление ссылки отдельным коммитом. Затем повторите его в CI с теми же правами доступа, что будут у production-сборки. Для справки по деталям команд используйте официальную документацию Git и раздел книги Pro Git о сабмодулях.
Сноска. 1 Gitlink — запись в дереве родительского репозитория, которая указывает на коммит сабмодуля, а не содержит копию всех его файлов.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Git — документация команды git-submodule, Pro Git — Git Tools: Submodules
Частые вопросы
Почему после клонирования сабмодуля нет файлов?
Нужно ли коммитить изменения в двух репозиториях?
Что будет, если не успеть обновить ссылку в родительском проекте?
Команды отличаются в другой версии Git?
Можно ли в CI собирать проект с приватным сабмодулем?
Почему git submodule update --remote изменил код неожиданно?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.