Олег Марков
Обновление подмодулей в Git - git submodule update
Введение
Подмодули в Git позволяют встроить один репозиторий внутрь другого как зависимость. Это удобно, когда вы хотите подключить стороннюю библиотеку как исходный код или разделить большой проект на несколько частей.
Но вместе с этим появляется вопрос - как правильно обновлять подмодули, чтобы:
- не потерять локальные изменения;
- не сломать сборку;
- не запутаться в версиях зависимостей;
- избежать конфликтов между основным репозиторием и подмодулями.
Здесь и вступает в игру команда git submodule update. Именно она отвечает за то, чтобы подмодули соответствовали тем версиям, которые зафиксированы в основном репозитории.
В этой статье вы увидите, как работает git submodule update, разберете ключевые флаги и типичные сценарии использования. Я буду опираться на практические примеры, чтобы вы могли сразу применить их в реальной работе.
Что такое подмодуль и что именно обновляет git submodule update
Подмодуль Git — это ссылка на другой Git‑репозиторий, зафиксированная в конкретном коммите. Важно понимать две вещи:
В основном репозитории хранится не сама история подмодуля, а только:
- URL подмодуля;
- путь к нему в рабочей директории;
- конкретный коммит, на который указывает подмодуль.
Подмодуль — это по сути отдельный репозиторий внутри папки проекта:
- у него есть свой .git (или ссылка на него);
- своя история;
- свои ветки.
Файл .gitmodules в корне проекта описывает подмодули. Пример конфигурации:
[submodule "libs/mylib"]
path = libs/mylib # Путь к подмодулю в проекте
url = git@github.com:org/mylib.git # Репозиторий подмодуля
branch = main # Необязательный параметр - используемая ветка
А в индексе Git основной репозиторий хранит только хеш коммита подмодуля. Смотрите, как это выглядит:
# Покажем статус подмодулей
git submodule status
# 3f2a9c7d8e1c0b4d5f6a7b8c9d0e1f2a3b4c5d6 libs/mylib
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ хеш коммита
# путь к подмодулю -> libs/mylib
Теперь главный момент:
- git submodule update не "обновляет" подмодуль до последнего коммита в его ветке;
- он синхронизирует рабочую копию подмодуля с тем коммитом, который уже записан в основном репозитории.
То есть команда подтягивает состояние подмодулей под то, что уже зафиксировано в команде разработчиков (в коммитах основного репозитория), а не "самостоятельно обновляет зависимости до новых версий".
Базовый синтаксис git submodule update
Давайте посмотрим на базовый синтаксис:
git submodule update [<путь_к_подмодулю> ...]
Без дополнительных флагов команда делает следующее:
Считывает информацию о подмодулях из:
- файла .gitmodules;
- индекса и HEAD основного репозитория.
Для каждого подмодуля:
- извлекает (fetch) нужные объекты из удаленного репозитория (если их нет локально);
- переключает рабочую копию подмодуля на тот коммит, который записан в основном репозитории;
- оставляет подмодуль в состоянии detached HEAD (голова отсоединена).
Простой пример:
# Обновить все уже инициализированные подмодули до нужных коммитов
git submodule update
Если вы хотите обновить только один конкретный подмодуль, укажите путь:
# Обновить только подмодуль в папке libs/mylib
git submodule update libs/mylib
Обратите внимание: если вы впервые клонировали репозиторий с подмодулями, простой git clone не скачает содержимое подмодулей. После клонирования вы увидите пустые папки или непроинициализированные директории. В этом случае используют связку:
git submodule init # Инициализация конфигурации подмодулей локально
git submodule update # Загрузка и установка нужных коммитов
Ниже мы разберем, как сократить это до одной команды и какие существуют расширенные режимы.
Клонирование репозитория с подмодулями и их начальное обновление
Быстрый способ - clone с параметром --recurse-submodules
Вместо последовательности из init и update чаще используют вариант:
git clone --recurse-submodules git@github.com:org/main-repo.git
# Параметр --recurse-submodules:
# - автоматически инициализирует подмодули
# - выполняет git submodule update для них
Смотрите, что происходит:
- Клонируется основной репозиторий.
- Читается файл .gitmodules.
- Для каждого подмодуля выполняется:
- настройка URL;
- загрузка содержимого;
- переключение на зафиксированный коммит.
Этот способ особенно удобен, когда вы подключаете новый проект и хотите сразу иметь рабочее состояние.
Когда нужен отдельный git submodule init
Команда git submodule init настраивает локальную конфигурацию подмодулей, но не загружает их содержимое. Это полезно, когда:
- файл .gitmodules был изменен, добавили новый подмодуль;
- вы уже работали с репозиторием, но кто-то из коллег добавил еще подмодули.
Пример:
# Коллега добавил подмодуль в проект
git pull origin main
# Теперь инициализируем только новые подмодули
git submodule init
# И затем подтягиваем их содержимое
git submodule update
Комбинация init + update по сути эквивалентна тому, что делает --recurse-submodules при клонировании, только применяется позже, уже в существующем рабочем репозитории.
Ключевые флаги git submodule update и когда их использовать
Теперь давайте перейдем к более интересным режимам работы git submodule update. Они задают поведение команды в разных сценариях.
--init — инициализация и обновление в одной команде
Флаг --init позволяет объединить инициализацию и обновление:
git submodule update --init
# Сначала инициализация подмодулей (как git submodule init)
# Затем загрузка и установка нужных коммитов
Вы можете указать конкретный подмодуль:
git submodule update --init libs/mylib
Такой вариант удобен, когда вы не уверены, инициализирован ли подмодуль у текущего разработчика, а хотите дать одну универсальную команду в инструкции.
Например, в README можно написать:
# Подготовка репозитория к сборке
git submodule update --init --recursive
Здесь сразу задействован еще один флаг — о нем дальше.
--recursive — рекурсивное обновление вложенных подмодулей
Подмодули сами могут содержать подмодули. Тогда простой git submodule update обновит только первый уровень, а вложенные останутся нетронутыми.
Флаг --recursive решает эту проблему:
git submodule update --init --recursive
# Инициализирует и обновляет:
# - подмодули первого уровня
# - подмодули внутри них
# - и так далее на всю глубину
Обратите внимание:
- Если вы работаете с проектами, где есть несколько уровней подмодулей (например, монорепозитории или наборы библиотек), используйте этот флаг почти всегда.
- Для CI-сборок это часто стандартная команда.
--remote — обновление подмодулей по ветке, а не по зафиксированному коммиту
Обычно git submodule update приводит подмодуль к конкретному коммиту, записанному в основном репозитории. Но иногда вы хотите обновлять подмодуль до последнего коммита в его ветке (например, в ветке main).
Для этого есть режим --remote:
git submodule update --remote
Что делает эта команда:
- Для каждого подмодуля:
- читает в .gitmodules, какая ветка указана в параметре branch;
- делает fetch изменений из origin;
- переключает подмодуль на последний коммит этой ветки;
- оставляет его в этой ветке (HEAD не будет detached).
Пример конфигурации:
[submodule "libs/mylib"]
path = libs/mylib
url = git@github.com:org/mylib.git
branch = main # Именно эта ветка используется с --remote
Пример использования:
# Обновить все подмодули до актуальных коммитов их веток
git submodule update --remote
# Обновить только конкретный подмодуль по ветке
git submodule update --remote libs/mylib
Обратите внимание на важный момент: после такого обновления вам нужно зафиксировать в основном репозитории новый коммит подмодуля:
cd libs/mylib
git log -1 # Смотрите, какой новый коммит
cd ..
git add libs/mylib
git commit -m "Обновлен подмодуль libs/mylib до актуальной версии"
Иначе ваши коллеги, выполнив обычный git submodule update (без --remote), не получат новый коммит, так как основной репозиторий все еще указывает на старое состояние.
--merge и --rebase — когда есть локальные изменения в подмодуле
Нередкая ситуация: вы вносите локальные изменения в подмодуль, а затем хотите обновить его под нужный коммит, не потеряв свою работу.
По умолчанию git submodule update:
- просто принудительно переключит подмодуль на нужный коммит;
- если в подмодуле есть незакоммиченные изменения, команда завершится с ошибкой.
Чтобы аккуратно совмещать локальные изменения с обновлениями, используются режимы --merge и --rebase.
--merge
Режим --merge делает следующее:
- Обновляет подмодуль до нужного коммита.
- Поверх этого делает merge с вашей текущей веткой.
Пример:
git submodule update --remote --merge libs/mylib
# 1. Обновит подмодуль до нового коммита ветки branch из .gitmodules
# 2. Попробует слить изменения с вашей текущей веткой в подмодуле
Как это выглядит в шаги:
cd libs/mylib
# Подмодуль переключен на новый коммит
# Git выполняет что-то вроде:
# git merge <старый_коммит/ваша_ветка>
Если будут конфликты, Git попросит их решить, вы сделаете обычный merge и commit.
--rebase
Режим --rebase похож, но история становится линейной:
git submodule update --remote --rebase libs/mylib
Здесь Git:
- Обновит подмодуль до нового коммита ветки.
- "Перенесет" ваши локальные коммиты поверх новых (rebase).
Это удобно, если вы хотите линейную историю без merge-коммитов внутри подмодулей.
Выбирайте:
- --merge — если вас устраивают merge-коммиты;
- --rebase — если вы любите чистую линейную историю.
Типичные сценарии использования git submodule update
Теперь давайте разберем практические сценарии, с которыми вы будете сталкиваться чаще всего.
Сценарий 1. Вы клонировали репозиторий с подмодулями
Рекомендуемая последовательность:
git clone --recurse-submodules git@github.com:org/main-repo.git
# Репозиторий и все подмодули готовы к работе
Если вы уже клонировали без параметра:
git clone git@github.com:org/main-repo.git
cd main-repo
# Инициализация и обновление всех подмодулей
git submodule update --init --recursive
Здесь я использую --recursive на случай наличия вложенных подмодулей.
Сценарий 2. Коллега обновил версию подмодуля, вы делаете git pull
Часто в команде кто-то обновляет подмодуль (фиксирует новый коммит в основном репозитории). Вы делаете:
git pull
После этого у вас в рабочей копии подмодуль может находиться "не на том" коммите. Статус будет выглядеть примерно так:
git status
# modified: libs/mylib (new commits)
Это не ваши изменения в подмодуле, а просто расхождение между:
- коммитом подмодуля в рабочей папке;
- коммитом, на который ссылается основной репозиторий.
Решение:
git submodule update --recursive
Теперь подмодуль будет приведен к коммиту, зафиксированному в полученном вами коммите основного репозитория.
Сценарий 3. Вы хотите обновить подмодуль до свежего состояния его ветки
Предположим:
- подмодуль указывает на ветку main;
- вы хотите подтянуть последние изменения из этой ветки.
Настройка в .gitmodules:
[submodule "libs/mylib"]
path = libs/mylib
url = git@github.com:org/mylib.git
branch = main
Далее вы выполняете:
# Обновить подмодуль до последнего коммита ветки main
git submodule update --remote libs/mylib
Теперь вы увидите новый коммит в подмодуле:
cd libs/mylib
git log -1 # Здесь уже последний коммит из origin/main
После этого важно:
cd ..
git add libs/mylib
git commit -m "Обновлен подмодуль libs/mylib до последней версии main"
git push
Теперь коллеги смогут просто выполнить:
git pull
git submodule update
и получить ваш обновленный подмодуль.
Сценарий 4. Подмодуль с локальными изменениями, а вам нужно обновить его версию
Представьте, что вы правили код в подмодуле, а в самом подмодуле появились новые коммиты от коллег.
Шаги:
- Сначала убедимся, что ваши изменения в подмодуле закоммичены:
cd libs/mylib
git status
# Если есть изменения - делаем коммит
git add .
git commit -m "Локальные правки в подмодуле"
- Теперь из основного репозитория:
cd ..
git submodule update --remote --rebase libs/mylib
# или --merge, если предпочитаете merge-историю
Разрешаем возможные конфликты в подмодуле:
- заходим в папку подмодуля;
- решаем конфликты;
- делаем git rebase --continue или git commit в зависимости от режима.
Фиксируем обновленный коммит подмодуля в основном репозитории:
cd ..
git add libs/mylib
git commit -m "Обновлен подмодуль libs/mylib с учетом локальных изменений"
Важные нюансы работы git submodule update
Detached HEAD в подмодулях — почему это нормально
После обычного git submodule update вы окажетесь в подмодуле в состоянии detached HEAD:
cd libs/mylib
git status
# HEAD detached at 3f2a9c7
Это значит:
- вы находитесь на конкретном коммите, не привязанном к локальной ветке;
- любые новые коммиты будут "висячими", если не создать ветку.
Для чтения кода и сборки это не проблема. Но если вы хотите вносить изменения в подмодуль, лучше переключиться на ветку:
cd libs/mylib
git checkout main # или другая нужная ветка
# Теперь можно коммитить как обычно
Если вы используете git submodule update --remote и в .gitmodules указана branch, то Git сразу будет переключать подмодуль на эту ветку, а не оставлять в detached HEAD.
Разница между git submodule update и git pull внутри подмодуля
Может возникнуть соблазн заходить в папку подмодуля и делать там git pull. Смотрите, чем это чревато:
- git pull действительно подтянет новые коммиты подмодуля;
- но основной репозиторий не узнает о том, что подмодуль теперь указывает на другой коммит, пока вы не сделаете git add путькподмодулю и git commit.
Если вы просто сделаете git pull в подмодуле и ничего не зафиксируете в основном проекте, коллеги не смогут воспроизвести ваше состояние одним git submodule update.
Правильный подход:
Если обновляете подмодуль как зависимость для проекта:
- делайте git submodule update --remote;
- фиксируйте новый коммит подмодуля в основном репозитории.
Если вы именно разрабатываете сам подмодуль:
- можете работать в нем как в обычном репозитории;
- но при смене "версии зависимости" для основного проекта все равно нужно обновить ссылку в основном репозитории.
Работа с несколькими удаленными репозиториями подмодуля
Иногда подмодуль имеет несколько remotes. git submodule update по умолчанию ориентируется на origin. Если вы хотите использовать другой remote, вам нужно:
- Зайти в подмодуль и настроить нужный remote.
- При необходимости изменить url в .gitmodules, чтобы остальные разработчики подтягивали то же самое.
Пример:
cd libs/mylib
git remote add upstream git@github.com:other-org/mylib.git
git fetch upstream
git checkout -b feature/upstream upstream/main
# После этого вы можете обновить подмодуль до нужного коммита
cd ..
git add libs/mylib
git commit -m "Переключен подмодуль libs/mylib на новую ветку"
git submodule update будет работать с тем коммитом, который вы зафиксируете в основном репозитории, независимо от того, с какого remote этот коммит был получен.
Практическая инструкция по работе с подмодулями и их обновлением
Здесь я соберу типичные команды в одном месте, чтобы вам было проще использовать их как шпаргалку.
Первичная настройка проекта с подмодулями
# Клонируем проект со всеми подмодулями
git clone --recurse-submodules git@github.com:org/main-repo.git
cd main-repo
# На всякий случай обновляем подмодули рекурсивно
git submodule update --init --recursive
Обновление подмодулей после git pull
# Получаем последние изменения основного репозитория
git pull
# Приводим подмодули к нужным коммитам
git submodule update --recursive
Обновление подмодуля до последнего коммита его ветки
# Убедитесь, что в .gitmodules указана ветка для подмодуля
# branch = main
# Обновляем подмодуль до конца ветки
git submodule update --remote libs/mylib
# Фиксируем новую версию подмодуля в основном репозитории
git add libs/mylib
git commit -m "Обновлен подмодуль libs/mylib до последних изменений main"
git push
Внесение правок в подмодуль и обновление его версии в проекте
# Переходим в подмодуль
cd libs/mylib
# Переключаемся на ветку разработки
git checkout main
# Вносим правки, коммитим
# ... ваши изменения ...
git add .
git commit -m "Исправление бага в библиотеке"
# Отправляем изменения в удаленный репозиторий подмодуля
git push origin main
# Возвращаемся в основной проект
cd ..
# Фиксируем новый коммит подмодуля
git add libs/mylib
git commit -m "Подмодуль libs/mylib обновлен до нового коммита main"
git push
Обновление подмодуля при наличии локальных коммитов
# Обновляем подмодуль до последней версии ветки и переносим наши коммиты поверх
git submodule update --remote --rebase libs/mylib
# Входим в подмодуль при необходимости решаем конфликты
cd libs/mylib
# ... разрешаем конфликты, выполняем git rebase --continue ...
cd ..
# Фиксируем обновленное состояние подмодуля
git add libs/mylib
git commit -m "Слияние локальных правок с обновленной версией подмодуля"
Заключение
Команда git submodule update — это ключевой инструмент при работе с подмодулями. Она не "искрещивает" миры, а просто делает следующее: приводит рабочие копии подмодулей к тем коммитам, которые зафиксированы в основном репозитории.
Если обобщить:
- без флагов команда синхронизирует подмодули с текущим коммитом основного проекта;
- --init упрощает начальную настройку;
- --recursive полезен, когда есть вложенные подмодули;
- --remote позволяет отслеживать ветки подмодулей, а не конкретные коммиты;
- --merge и --rebase помогают аккуратно обновлять подмодули при наличии локальных изменений.
Главная идея: состояние подмодуля в сборке должно однозначно определяться коммитом основного репозитория. Поэтому, обновляя подмодуль, не забывайте фиксировать изменения в основном проекте. Тогда другие разработчики смогут воспроизводить вашу среду одной командой: git submodule update.
Частозадаваемые технические вопросы по теме
Вопрос 1. Как удалить подмодуль и чтобы git submodule update больше его не трогал
- Удалите запись из .gitmodules.
- Удалите запись о подмодуле из конфигурации Git:
- выполните команду
git config -f .git/config --remove-section submodule.pathкподмодулю
(подставьте правильный путь).
- выполните команду
- Удалите сам каталог подмодуля:
- rm -rf pathкподмодулю.
- Удалите ссылку на подмодуль из индекса:
- git rm --cached pathкподмодулю.
- Сделайте коммит с этими изменениями.
После этого git submodule update больше не будет пытаться работать с этим путем.
Вопрос 2. Почему git submodule update ничего не делает после изменения .gitmodules
Если вы только добавили подмодуль в .gitmodules или изменили его URL, но не выполнили git submodule init или не добавили файл .gitmodules в индекс и не закоммитили его, Git может "не видеть" изменений. Сделайте:
- git add .gitmodules.
- git commit -m "Обновлена конфигурация подмодулей".
- Затем выполните git submodule update --init --recursive.
Теперь Git возьмет информацию из .gitmodules и корректно инициализирует и обновит подмодуль.
Вопрос 3. Как сделать так, чтобы CI всегда подтягивал подмодули правильно
Обычно в скриптах CI используют следующую последовательность:
- git submodule sync --recursive
Синхронизирует локальные URL подмодулей с .gitmodules. - git submodule update --init --recursive
Инициализирует отсутствующие подмодули и обновляет все уровни.
Эти две команды делают пайплайн устойчивым к изменениям URL и добавлению новых подмодулей.
Вопрос 4. Что делать, если подмодуль указывает на несуществующий коммит
Такое случается, если:
- кто-то в истории переписал подмодуль (rebase, force push);
- основной репозиторий указывает на коммит, которого больше нет в удаленном репозитории подмодуля.
При git submodule update вы получите ошибку, что объект не найден. Решение:
- Уточните у команды, доступен ли этот коммит в каком-нибудь другом remote.
- Если нет, переключите подмодуль на ближайший подходящий коммит или ветку.
- Зафиксируйте новый коммит подмодуля в основном репозитории:
git add pathкподмодулю и git commit.
После этого git submodule update будет работать с новым валидным коммитом.
Вопрос 5. Можно ли запретить git submodule update изменять определенный подмодуль
Напрямую "запретить" нельзя, но можно добиться похожего эффекта:
- Исключите подмодуль из автоматических скриптов, явно не указывая его в командах.
- Если вы не хотите, чтобы подмодуль обновлялся при git submodule update без аргументов, держите этот подмодуль в отдельной ветке или используйте отдельные инструкции для его обновления.
- Для локальной работы можно временно "отвязать" каталог подмодуля, переименовав его или удалив запись о нем из .git/config, но это скорее обходной путь и использовать его стоит аккуратно.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев