Инструкция

Git submodule: как добавить, обновить и удалить подмодули

Обновлено: · Skillaria.Top

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
Краткая сводка в statusgit 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 проекта:

  1. Клонировать только с --recurse-submodules и включить submodule.recurse true у всех.
  2. Обновлять подмодуль отдельным коммитом с понятным сообщением: какая версия и зачем.
  3. Не править код подмодуля в родительском проекте без отдельного коммита и push в сам подмодуль.
  4. В 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 с нуля до уверенного разработчика».

Перейти к курсу