Git submodule: как добавить, обновить и удалить подмодули
Git submodule — это другой репозиторий, вложенный в ваш и закреплённый на конкретном коммите. Подмодуль добавляют командой git submodule add <url> <путь>, получают при клонировании через git clone --recurse-submodules, обновляют через git submodule update --remote, а удаляют через git submodule deinit и git rm.
Git submodule (подмодуль) — это отдельный Git-репозиторий, вложенный в папку вашего проекта. Главное его свойство: родительский репозиторий хранит не файлы подмодуля, а ссылку на конкретный коммит в нём. Поэтому у всех, кто клонирует проект, подмодуль окажется в одной и той же версии — пока вы сами явно её не поменяете.
Подмодули используют, когда нужно подключить чужую библиотеку без пакетного менеджера, разделить общий код между несколькими проектами, вынести тему оформления или прошивку в отдельный репозиторий с собственной историей.
Как устроены git submodules
Подмодуль состоит из трёх частей, и понимание их связи снимает большую часть проблем:
- Файл
.gitmodulesв корне проекта — коммитится и описывает, какой путь из какого URL берётся. - Запись gitlink в дереве коммита — специальный элемент с режимом
160000, где вместо содержимого лежит хеш коммита подмодуля. - Сам репозиторий подмодуля — его данные хранятся в
.git/modules/<имя>родителя, а в папке подмодуля лежит файл.gitсо ссылкой туда.
cat .gitmodules
[submodule "libs/ui-kit"]
path = libs/ui-kit
url = https://github.com/example/ui-kit.git
git ls-tree HEAD libs/
160000 commit 8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c libs/ui-kit
Обратите внимание: родитель фиксирует именно коммит, а не ветку. Это фундаментальное отличие submodule git от зависимости «последняя версия из main».
Как добавить подмодуль
git submodule add https://github.com/example/ui-kit.git libs/ui-kit
Cloning into '/home/dev/shop/libs/ui-kit'...
done.
git status
new file: .gitmodules
new file: libs/ui-kit
git commit -m "Подключить ui-kit как подмодуль"
Команда клонирует репозиторий, создаёт или дополняет .gitmodules и добавляет gitlink в индекс. Остаётся закоммитить. Если путь не указать, папка будет названа по имени репозитория.
Чтобы подмодуль при обновлении следил за определённой веткой, укажите её сразу: git submodule add -b develop <url> <путь>. Ветка запишется в .gitmodules как branch = develop.
Относительный URL
Если подмодуль лежит на том же сервере, можно указать относительный путь: git submodule add ../ui-kit.git libs/ui-kit. Он разрешается относительно URL удалённого репозитория родителя, поэтому работает и через SSH, и через HTTPS — смотря как клонировали проект.
Как клонировать проект с подмодулями
Самая частая жалоба: «склонировал, а папка подмодуля пустая». По умолчанию git clone подмодули не скачивает. Правильно так:
git clone --recurse-submodules https://github.com/example/shop.git
Если проект уже склонирован без подмодулей:
git submodule update --init --recursive
--init регистрирует подмодули из .gitmodules в локальном .git/config, update скачивает их и переключает на зафиксированный коммит, --recursive делает то же для подмодулей внутри подмодулей.
Как обновить подмодуль
Здесь важно различать две задачи: «получить ту версию, которую закоммитили коллеги» и «перейти на новую версию библиотеки».
Получить версию, зафиксированную в проекте
После git pull в родителе gitlink мог измениться, но файлы подмодуля сами не обновятся — git status покажет modified: libs/ui-kit (new commits). Синхронизируйте:
git pull
git submodule update --init --recursive
Чтобы не делать это руками, включите автоматику: git config submodule.recurse true. Тогда pull, checkout и switch будут обновлять подмодули сами. Альтернатива для разового вызова — git pull --recurse-submodules.
Перейти на новую версию библиотеки
# Вариант 1: подтянуть последний коммит отслеживаемой ветки
git submodule update --remote libs/ui-kit
# Вариант 2: вручную выбрать версию
cd libs/ui-kit
git fetch
git checkout v3.2.0
cd ../..
# В обоих случаях — зафиксировать новую версию в родителе
git add libs/ui-kit
git commit -m "Обновить ui-kit до v3.2.0"
--remote берёт ветку из параметра branch в .gitmodules, а если его нет — ветку, на которую указывает HEAD удалённого репозитория подмодуля (обычно main). Без последнего коммита в родителе коллеги новую версию не получат: обновление подмодуля — это всегда изменение gitlink, которое нужно закоммитить.
Посмотреть, что изменилось в подмодулях, удобно так:
git diff --submodule=log
git config diff.submodule log # сделать такой вывод форматом по умолчанию
git submodule status
8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c libs/ui-kit (v3.1.0)
+1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b libs/charts (v1.4.0-3-g1a2b3c4)
Префикс в git submodule status: - — подмодуль не инициализирован, + — его текущий коммит отличается от зафиксированного в родителе, U — конфликт слияния.
Как вносить изменения в подмодуль
После git submodule update подмодуль находится в состоянии detached HEAD — на конкретном коммите, а не на ветке. Если начать коммитить прямо так, коммиты легко потерять при следующем update. Порядок работы:
cd libs/ui-kit
git switch main # встать на ветку
# ... правки ...
git commit -am "Исправить отступы кнопки"
git push # сначала пушим подмодуль!
cd ../..
git add libs/ui-kit
git commit -m "ui-kit: исправлены отступы кнопки"
git push
Порядок push критичен. Если отправить родителя раньше подмодуля, на сервере проект будет ссылаться на коммит, которого никто, кроме вас, не может скачать, и у коллег git submodule update упадёт с ошибкой fatal: remote error: upload-pack: not our ref или did not contain. Защититься помогает git push --recurse-submodules=check (откажет в push, если подмодуль не отправлен) или =on-demand (сам отправит подмодули первыми).
Как удалить подмодуль
Раньше удаление требовало ручной правки нескольких файлов. В современных версиях Git достаточно трёх шагов:
git submodule deinit -f libs/ui-kit # убрать из .git/config и очистить папку
git rm -f libs/ui-kit # убрать gitlink и запись из .gitmodules
rm -rf .git/modules/libs/ui-kit # удалить сохранённые данные подмодуля
git commit -m "Удалить подмодуль ui-kit"
Последний шаг необязателен для корректности, но без него при повторном добавлении подмодуля по тому же пути Git может ругаться, что локальная папка уже существует (A git directory for 'libs/ui-kit' is found locally).
Если поменялся URL подмодуля
Репозиторий переехал — исправьте .gitmodules и синхронизируйте локальную конфигурацию:
git submodule set-url libs/ui-kit https://gitlab.com/example/ui-kit.git
git submodule sync --recursive
git commit -am "Новый адрес ui-kit"
Коллегам после pull тоже нужно выполнить git submodule sync: URL из .gitmodules копируется в их .git/config только при init или sync.
Полезные команды для нескольких подмодулей
| Задача | Команда |
|---|---|
| Список и состояние | git submodule status --recursive |
| Выполнить команду в каждом | git submodule foreach 'git switch main && git pull' |
| Ускорить скачивание | git submodule update --init --jobs 4 |
| Скачать без истории | git submodule update --init --depth 1 |
| Краткая сводка в status | git config status.submoduleSummary true |
Типичные проблемы с подмодулями
- Пустая папка после клонирования — не выполнен init. Решение:
git submodule update --init --recursive. - «modified: (new commits)» сразу после pull — родитель указывает на другой коммит, а файлы старые. Решение:
git submodule update. - Случайно откатили подмодуль — выполнили
git add ., когда подмодуль был на старом коммите, и закоммитили откат версии. Всегда проверяйтеgit diff --submoduleперед коммитом. - Конфликт gitlink при слиянии — в двух ветках подмодуль обновили до разных коммитов. Зайдите в подмодуль, выберите нужный коммит (часто более новый, включающий оба изменения), затем
git add путьи завершите merge. - Приватный подмодуль в CI — сборка не может скачать подмодуль без доступа. Нужно выдать CI ключ или токен к репозиторию подмодуля; относительные URL упрощают настройку.
Подмодули в команде: договорённости
Большая часть проблем с подмодулями возникает не из-за Git, а из-за того, что участники действуют по-разному. Несколько правил, которые стоит закрепить в README проекта:
- Клонировать только с
--recurse-submodulesи включитьsubmodule.recurse trueу всех. - Обновлять подмодуль отдельным коммитом с понятным сообщением: какая версия и зачем.
- Не править код подмодуля в родительском проекте без отдельного коммита и push в сам подмодуль.
- В CI явно выполнять
git submodule update --init --recursiveи проверять, что сборка не зависит от локальных изменений.
Для проверки в CI, что все зафиксированные коммиты подмодулей действительно доступны, достаточно чистого клона: если кто-то забыл отправить подмодуль, сборка упадёт на шаге update, а не у коллеги через неделю.
git clone --recurse-submodules --jobs 4 "$REPO_URL" build
cd build
git submodule status --recursive
Submodule или альтернативы
Подмодули дают точный контроль версии и раздельную историю, но добавляют ручные шаги каждому участнику. Если код — это обычная библиотека, проще пакетный менеджер (npm, pip, Composer, Go modules). Если важно, чтобы чужой код просто лежал внутри проекта и клонировался без лишних команд, рассмотрите git subtree: он копирует историю в основной репозиторий, и коллегам ничего не нужно знать. Для большого числа связанных сервисов команды часто выбирают монорепозиторий. Подмодуль оправдан, когда у вложенного кода своя жизнь, свои релизы и своя команда.
Итог
Git submodule — это ссылка на конкретный коммит другого репозитория. Добавление — git submodule add, получение — --recurse-submodules или update --init, обновление до новой версии — update --remote плюс коммит в родителе, удаление — deinit, git rm и очистка .git/modules. Помните про detached HEAD внутри подмодуля и порядок push. Продвинутые инструменты Git разобраны на практике в курсе по Git.
Частые вопросы
Почему подмодуль всегда в detached HEAD?
Родитель хранит хеш коммита, а не ветку, и git submodule update переключает подмодуль именно на этот коммит. Для правок переключитесь на ветку вручную или используйте git submodule update --remote --merge.
Можно ли зафиксировать подмодуль на теге?
Напрямую нет, но можно перейти в подмодуле на тег через git checkout и закоммитить в родителе получившийся коммит; в git submodule status тег будет виден в скобках.
Скачивает ли GitHub подмодули в архиве ZIP?
Нет, архивы исходников GitHub не включают содержимое подмодулей, папки будут пустыми. Используйте git clone --recurse-submodules.
Чем git submodule отличается от git subtree?
Submodule хранит ссылку на коммит внешнего репозитория и требует отдельных команд для получения кода, а subtree копирует файлы и историю прямо в основной репозиторий.
Можно ли переместить подмодуль в другую папку?
Да, командой git mv old/path new/path: Git сам обновит .gitmodules и внутренние ссылки, останется закоммитить.
Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».
Перейти к курсу