Олег Марков
Клонирование с подмодулями с помощью git clone --recursive
Введение
Клонирование репозитория с подмодулями в Git часто вызывает вопросы даже у опытных разработчиков. Кажется, что команда git clone уже должна "всё сделать сама", но при наличии подмодулей в проекте появляются дополнительные нюансы. Если не учитывать их, вы можете получить половину проекта без важных зависимостей, сломанные сборки и непонятные ошибки.
Команда git clone --recursive как раз и нужна для того, чтобы при клонировании основного репозитория сразу подтянуть все его подмодули в нужные версии. В этой статье мы разберём, как она работает, в каких случаях стоит её использовать, чем она отличается от альтернативных подходов и какие подводные камни стоит учитывать.
Смотрите, я покажу вам, как это работает на практике, с разбором конфигурации подмодулей и типичных сценариев использования.
Что такое подмодули в Git и зачем нужен флаг --recursive
Прежде чем говорить про git clone --recursive, важно кратко понять, что такое подмодуль.
Подмодуль Git — это ссылка на другой Git‑репозиторий, который "встроен" в ваш основной проект как подкаталог. Репозиторий-подмодуль живёт своей жизнью, имеет собственные коммиты, теги, ветки, но основной репозиторий хранит только "указатель" на конкретный коммит подмодуля.
Настройки подмодулей хранятся в специальном файле .gitmodules. Там указан URL и путь, по которому этот подмодуль должен быть размещён в рабочем дереве.
Пример содержимого .gitmodules:
[submodule "libs/logger"]
path = libs/logger ; Путь, по которому подмодуль появится в дереве
url = https://github.com/example/logger.git ; Репозиторий подмодуля
Без флага --recursive при обычном клонировании:
git clone https://github.com/example/main-repo.git
вы получите основной репозиторий, но каталоги подмодулей будут пустыми директориями или "псевдокаталогами" без содержимого. На файловой системе в большинстве случаев вы увидите директорию, внутри которой нет файлов исходного кода.
Флаг --recursive говорит Git:
- прочитать .gitmodules;
- для каждого объявленного подмодуля:
- клонировать его репозиторий;
- checkout‑нуть нужный коммит (тот, на который ссылается основной репозиторий);
- разместить его по указанному пути внутри рабочего дерева.
Таким образом, вы сразу получаете полностью "собранный" проект со всеми подмодулями в консистентных версиях.
Базовый синтаксис git clone --recursive
Начнём с базового примера, на котором проще всего увидеть разницу.
Обычное клонирование без подмодулей
git clone https://github.com/example/main-repo.git
cd main-repo
ls
# Здесь вы увидите структуру основного репозитория,
# но каталоги подмодулей будут пустыми или "неинициализированными"
Внутри папки подмодуля у вас не будет исходников. Если попробовать собрать проект или запустить тесты, скорее всего вы столкнётесь с ошибками "файл не найден" или "модуль не найден".
Клонирование с подмодулями
Теперь давайте сделаем то же самое, но с флагом --recursive:
git clone --recursive https://github.com/example/main-repo.git
cd main-repo
ls
# Теперь в каталогах подмодулей уже будут исходники,
# соответствующие конкретным коммитам
Git автоматически:
- Склонирует основной репозиторий.
- Найдёт файл .gitmodules.
- Для каждого подмодуля выполнит:
- clone по указанному URL;
- checkout того коммита, на который ссылается основной репозиторий.
Если вы посмотрите внутри подмодуля:
cd libs/logger
git status
# Вы увидите, что находитесь в отдельном репозитории,
# у которого своя история, ветки и статус.
Обратите внимание, что подмодуль — это полноценный Git‑репозиторий, просто размещённый внутри другого.
Как работает git clone --recursive "под капотом"
Флаг --recursive по сути — удобная обёртка над командой git submodule update --init --recursive, автоматически вызываемой после клонирования основного репозитория.
Схематично можно представить так:
git clone --recursive <url>
# Эквивалентно примерно следующему набору действий:
git clone <url> <target-dir>
cd <target-dir>
git submodule update --init --recursive
Разберём эти шаги подробнее.
Шаг 1. Клонирование основного репозитория
Git создаёт локальную копию основного репозитория, включая историю и все ветки.
git clone <url> <target-dir>
Шаг 2. Чтение файла .gitmodules
После клонирования Git ищет в корне (и подкаталогах) файл .gitmodules. В нём хранится информация:
- имя подмодуля (логическое, для Git);
- путь, по которому он должен быть размещён;
- URL репозитория подмодуля.
Git использует эти данные, чтобы знать, какие подмодули и откуда нужно подтянуть.
Шаг 3. Инициализация подмодулей
Команда git submodule update --init:
- создаёт для каждого подмодуля конфигурацию в .git/config;
- подготавливает локальный каталог подмодуля;
- клонирует сам репозиторий подмодуля.
Комментарий в духе:
git submodule update --init
# --init говорит Git инициализировать подмодули,
# которые объявлены, но ещё не настроены локально
Шаг 4. Рекурсивное обновление вложенных подмодулей
Флаг --recursive в git submodule update означает, что Git будет "проваливаться" внутрь каждого подмодуля и искать там свои подмодули, повторяя процедуру.
git submodule update --init --recursive
# Для каждого подмодуля:
# - клонировать его
# - затем посмотреть, есть ли у него свой .gitmodules
# - если есть, повторить процесс
Команда git clone --recursive объединяет все эти шаги в одну операцию. Это особенно удобно, когда структура подмодулей многоуровневая.
Когда обязательно использовать git clone --recursive
Давайте посмотрим на пару типичных сценариев, когда без --recursive вполне можно случайно "сломать" себе рабочую среду.
Сценарий 1. Монорепозиторий с внешними библиотеками
Представьте, что у вас есть основной проект, а часть библиотек подключается как подмодули (например, сторонние компоненты UI или общие библиотеки вашей команды).
Если вы выполните клонирование без подмодулей, дерево проекта будет выглядеть визуально нормально, но при сборке вы получите ошибки:
- отсутствуют исходники модулей;
- не найдены заголовочные файлы;
- отсутствуют реализации интерфейсов.
С git clone --recursive вы сразу подтянете библиотеки в правильных версиях, которые проверены и зафиксированы в основном репозитории.
Сценарий 2. CI/CD и сборочные сервера
На CI‑сервере (GitLab CI, GitHub Actions, Jenkins и т.п.) распространённая ошибка: pipeline клонирует только основной репозиторий, а подмодули игнорируются. В результате:
- тесты падают;
- сборка ломается;
- разработчики тратят время на поиск причины.
Решение — либо настроить систему CI на использование git clone --recursive, либо отдельно вызывать git submodule update --init --recursive после клонирования. Мы ещё вернёмся к этому в практических примерах.
Сценарий 3. Репозиторий SDK с примерами
Часто встречается структура, когда основная библиотека лежит в отдельных репозиториях, а репозиторий с примерами подключает эти библиотеки как подмодули. При клонировании без --recursive примеры выглядят "пустыми".
С git clone --recursive вы сразу получите рабочие примеры, которые можно запускать и изучать.
Альтернатива: git submodule update --init --recursive после клонирования
Если вы по какой-то причине забыли указать --recursive при клонировании, не нужно удалять репозиторий и начинать заново. Можно доинициализировать подмодули вручную.
Пошаговая инструкция
Клонируете репозиторий как обычно:
git clone https://github.com/example/main-repo.git cd main-repoИнициализируете и обновляете подмодули:
git submodule update --init --recursive
Комментарии покажут, что делает команда:
git submodule update --init --recursive
# --init: создать локальные записи о подмодулях,
# если они ещё не инициализированы
# --recursive: рекурсивно пройти по всем вложенным подмодулям
# update: скачать нужные коммиты подмодулей, соответствующие
# текущему коммиту основного репозитория
Фактически вы вручную воспроизводите то, что сделал бы git clone --recursive.
Когда этот подход удобнее
Иногда вам не хочется автоматически тянуть все подмодули (например, их очень много, а нужны только некоторые). В этом случае вы можете:
- клонировать без --recursive;
- инициализировать только нужные подмодули.
Пример:
# Клонируем без подмодулей
git clone https://github.com/example/main-repo.git
cd main-repo
# Инициализируем только один подмодуль
git submodule update --init path/to/needed-submodule
Комментарий к команде:
# Здесь мы явно указываем путь к конкретному подмодулю,
# поэтому Git не трогает остальные подмодули
Так вы экономите трафик и время, если проект большой, а работать нужно только с частью его компонентов.
Взаимодействие с ветками и подмодулями при клонировании
Теперь давайте посмотрим, что происходит, если вы клонируете конкретную ветку или коммит, и как это связано с подмодулями.
Клонирование ветки с подмодулями
Если вы клонируете репозиторий с указанием ветки, подмодули будут подтянуты в тех версиях, которые зафиксированы в этой ветке.
Пример:
git clone --branch develop --recursive https://github.com/example/main-repo.git
Здесь происходит следующее:
- клонируется ветка develop основного репозитория;
- подмодули инициализируются и переводятся в те коммиты, которые привязаны к этой ветке.
Важно понимать: подмодули не обязательно будут находиться на какой-то "ветке" в привычном смысле. Часто это detached HEAD на конкретном коммите. Это поведение нормально и ожидаемо.
Проверьте внутри подмодуля:
cd libs/logger
git status
# Скорее всего увидите detached HEAD,
# потому что основной репозиторий "заставляет" подмодуль держать конкретный коммит
Переключение веток и обновление подмодулей
После клонирования вы можете переключаться между ветками основного репозитория. При этом указатели на подмодули могут меняться.
Чтобы подмодули соответствовали выбранной ветке, рекомендуется после смены ветки выполнять:
git checkout feature/new-api
git submodule update --init --recursive
# Эта команда подтянет те версии подмодулей, которые нужны для новой ветки
Если этого не сделать, подмодули могут остаться в старом состоянии, и проект собираться не будет.
Частые проблемы при использовании git clone --recursive
Теперь давайте разберём типичные проблемы и их решения. Это как раз те случаи, когда всё "почти работает", но что-то идёт не так.
Проблема 1. Ошибки доступа к подмодулям (SSH vs HTTPS)
Ситуация: вы успешно клонируете основной репозиторий, но git clone --recursive падает с ошибкой доступа к одному из подмодулей.
Частая причина — в .gitmodules указан URL по SSH, а у вас:
- нет настроенных SSH‑ключей;
- или нет доступа к этому репозиторию.
Пример ошибки:
Permission denied (publickey).
fatal: Could not read from remote repository.
Как можно решить:
Проверить содержимое .gitmodules:
cat .gitmodulesЕсли там SSH‑URL, а вам нужен HTTPS, можно изменить URL локально:
git submodule set-url libs/logger https://github.com/example/logger.git git submodule sync git submodule update --init --recursive
Комментарии по шагам:
# set-url - меняет URL подмодуля в конфигурации
# sync - синхронизирует данные из .gitmodules в .git/config
# update - заново пытается подтянуть подмодуль с обновлённым URL
Если вы администратор проекта, то лучше исправить URL в самом .gitmodules и закоммитить изменение, чтобы команда не сталкивалась с этой проблемой.
Проблема 2. Подмодуль не обновляется до ожидаемой версии
Ситуация: вы клонировали репозиторий с --recursive, но внутри подмодуля видите "старый" код.
Чаще всего причина в том, что основной репозиторий указывает на старый коммит подмодуля. Git при клонировании делает именно то, что написано в основном репозитории — он не пытается автоматически перейти на "последнюю" версию подмодуля.
Проверить, на какой коммит ссылается основной репозиторий, можно так:
# Находим хэш подмодуля, зафиксированный в основном репозитории
git ls-tree HEAD path/to/submodule
Вы увидите строку с хэшем коммита подмодуля. Именно этот коммит и будет checkout‑нут при clone --recursive.
Если вам нужно обновить подмодуль до свежей версии, это уже отдельная операция:
cd path/to/submodule
git checkout main # Переходим на нужную ветку
git pull # Подтягиваем последние изменения
cd ..
git add path/to/submodule # Фиксируем обновлённое состояние подмодуля
git commit -m "Обновлен подмодуль до последней версии"
git push
После этого новые клонирования с --recursive будут получать уже обновлённую версию подмодуля.
Проблема 3. Огромный размер репозитория из-за подмодулей
Иногда подмодули содержат большие бинарные файлы, историю артефактов и т.п. При git clone --recursive вы тянете весь их исторический багаж. Это может быть не нужно.
В таких случаях можно использовать флаг --depth для "поверхностного" клонирования, но нужно учитывать несколько особенностей.
Пример:
git clone --recursive --depth 1 https://github.com/example/main-repo.git
Комментарий:
# --depth 1 говорит Git клонировать только последний коммит
# основной ветки и не тянуть всю историю
Однако важно:
- для подмодулей глубина по умолчанию может отличаться;
- при попытке переключиться на старый коммит у вас может не быть нужной истории.
Если вы точно знаете, что история вам не нужна (например, на CI‑сервере для одноразовой сборки), это хороший способ ускорить клонирование и сократить трафик.
Практические сценарии использования git clone --recursive
Теперь давайте посмотрим, как git clone --recursive выглядит в разных окружениях и сценариях.
Сценарий: настройка рабочего окружения нового разработчика
Представьте, что к проекту подключается новый разработчик. Чтобы минимизировать количество шагов, вы можете описать в документации всего пару команд.
Пример инструкции:
# 1. Клонировать основной репозиторий вместе со всеми подмодулями
git clone --recursive git@github.com:company/main-repo.git
# 2. Перейти в директорию проекта
cd main-repo
# 3. (Опционально) переключиться на нужную ветку
git checkout develop
# 4. (Рекомендуется) синхронизировать подмодули после смены ветки
git submodule update --init --recursive
Комментарий:
# Здесь мы намеренно дублируем шаг с git submodule update,
# чтобы гарантировать корректное состояние подмодулей после переключения ветки.
Такой сценарий уменьшает количество проблем "у меня не собирается, потому что подмодуль не подтянулся".
Сценарий: использование в CI (GitLab CI, GitHub Actions и др.)
Теперь вы увидите, как это выглядит в автоматической сборке.
Пример конфигурации GitLab CI
# .gitlab-ci.yml
variables:
GIT_SUBMODULE_STRATEGY: recursive # GitLab сам выполнит clone с подмодулями
build:
stage: build
script:
- echo "Сборка проекта..."
- make build
Здесь GitLab при подготовке среды самостоятельно выполнит клонирование с подмодулями. Фактически он делает поведение, сопоставимое с git clone --recursive и git submodule update --init --recursive.
Если же вы хотите управлять этим вручную, можно использовать обычный git clone:
build:
stage: build
script:
- git clone --recursive "$CI_REPOSITORY_URL" .
- echo "Сборка проекта..."
- make build
Пример для GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Клонировать репозиторий с подмодулями
uses: actions/checkout@v4
with:
submodules: recursive # Включаем рекурсивные подмодули
fetch-depth: 0 # При необходимости - полная история
- name: Сборка проекта
run: |
echo "Сборка..."
make build
Комментарий:
# submodules: recursive - GitHub Actions при checkout подтянет все подмодули
# на нужные версии, аналогично clone --recursive.
Сценарий: частичное использование подмодулей
Иногда в проекте очень много подмодулей, но в конкретной задаче вам нужен только один или два. В таком случае, как мы уже обсуждали, можно не использовать --recursive при клонировании.
Алгоритм:
Клонируете основной репозиторий без подмодулей:
git clone https://github.com/example/big-repo.git cd big-repoСмотрите список подмодулей:
cat .gitmodulesИнициализируете только нужные:
git submodule update --init libs/logger git submodule update --init components/ui-kit
Комментарий:
# Здесь мы явно указываем пути к двум подмодулям,
# которые нужны для текущей задачи. Остальные не трогаем.
Такой подход особенно полезен, если подмодули сами по себе очень тяжёлые и редко используются.
Советы по организации репозитория с подмодулями
Флаг git clone --recursive — это только одна сторона вопроса. Чтобы он работал предсказуемо, стоит правильно организовать репозиторий.
Поддерживайте актуальность файла .gitmodules
Файл .gitmodules должен быть:
- закоммичен в основной репозиторий;
- содержать актуальные URL и пути;
- синхронизирован с реальным положением каталогов.
При изменении пути подмодуля не забывайте обновить .gitmodules и использовать git submodule sync, чтобы синхронизировать настройки.
Пример:
# Переезд подмодуля в другое место
git mv libs/logger vendor/logger
# Обновляем конфиг подмодуля
git config -f .gitmodules submodule.libs/logger.path vendor/logger
# Синхронизируем настройки
git submodule sync
git add .gitmodules
git commit -m "Перенос подмодуля logger в vendor/logger"
Комментарии:
# git config -f .gitmodules - редактирует файл .gitmodules как конфигурационный
# submodule.sync - приводит локальные настройки в соответствие с .gitmodules
Явно документируйте необходимость подмодулей
В README имеет смысл прямо написать, что для корректного клонирования проекта нужно использовать --recursive, особенно если без подмодулей проект вообще не собирается.
Пример фрагмента README:
Клонирование репозитория
Для корректной работы проекта необходимо клонировать его вместе с подмодулями:
git clone --recursive git@github.com:company/main-repo.git
Если вы уже клонировали репозиторий без подмодулей, выполните:
git submodule update --init --recursive
Так вы сразу снимаете часть вопросов у новых участников команды.
Краткое резюме по git clone --recursive
- git clone --recursive используется для клонирования репозитория вместе со всеми его подмодулями, включая вложенные.
- Подмодули описываются в файле .gitmodules, где указаны их пути и URL.
- Внутри подмодулей Git фиксирует конкретные коммиты, поэтому при клонировании вы получаете "замороженное" состояние зависимостей, а не "последние" версии.
- Альтернативой --recursive является последовательное выполнение git submodule update --init --recursive после обычного git clone.
- При переключении веток основного репозитория рекомендуется обновлять подмодули командой git submodule update --init --recursive, чтобы версии соответствовали выбранной ветке.
- В CI‑сценариях нужно явно включать работу с подмодулями (через clone --recursive или настройки конкретной системы CI).
Используя git clone --recursive и связанные с ним команды осознанно, вы уменьшаете количество "мистических" ошибок и получаете предсказуемую, воспроизводимую среду сборки.
Частозадаваемые технические вопросы по теме и ответы
Вопрос 1. Как склонировать репозиторий с подмодулями, но не тянуть историю подмодулей целиком?
Используйте поверхностное клонирование с ограничением глубины как для основного репозитория, так и для подмодулей:
git clone --recursive --depth 1 https://github.com/example/main-repo.git
cd main-repo
git submodule update --init --recursive --depth 1
Так вы получите только последние коммиты без всей истории. Этот способ хорош для CI и одноразовых сборок.
Вопрос 2. Что делать, если один из подмодулей больше не нужен, а git clone --recursive продолжает пытаться его клонировать?
Нужно удалить подмодуль корректно:
# 1. Удалить запись из .gitmodules
git rm path/to/submodule
git commit -m "Удалён подмодуль"
# 2. Удалить запись из .git/config (при необходимости вручную)
git config --remove-section submodule.path/to/submodule
# 3. Удалить директорию .git/modules/path/to/submodule, если она осталась
rm -rf .git/modules/path/to/submodule
После этих действий git clone --recursive больше не будет пытаться подтягивать удалённый подмодуль.
Вопрос 3. Как склонировать репозиторий с подмодулями, используя разные протоколы для основного репозитория и подмодулей (например, HTTPS для подмодулей и SSH для основного)?
После клонирования основного репозитория по SSH измените URL подмодулей на HTTPS и выполните обновление:
git clone git@github.com:company/main-repo.git
cd main-repo
# Меняем URL конкретного подмодуля на HTTPS
git submodule set-url libs/logger https://github.com/company/logger.git
git submodule sync
# Инициализируем и обновляем подмодули
git submodule update --init --recursive
Так вы можете гибко комбинировать способы доступа.
Вопрос 4. Как заставить подмодуль всегда использовать определённую ветку при обновлении, а не просто конкретный коммит?
В стандартной модели подмодулей Git хранит именно коммит, а не ветку. Но можно настроить "tracking"‑поведение:
# В конфигурации основного репозитория
git config -f .gitmodules submodule.libs/logger.branch main
git submodule sync
# При обновлении
git submodule update --remote --merge libs/logger
Флаг --remote говорит Git подтянуть последний коммит указанной ветки (branch в .gitmodules), а не использовать зафиксированный хэш. Однако такое поведение менее детерминированно, поэтому использовать его стоит осторожно.
Вопрос 5. Как обновить подмодули до последних коммитов удалённых веток сразу при клонировании, а не после?
Прямо при git clone это сделать нельзя, но можно выполнить последовательность команд сразу после клонирования:
git clone --recursive https://github.com/example/main-repo.git
cd main-repo
# Обновить все подмодули до последних коммитов отслеживаемых веток
git submodule update --remote --recursive
При этом для каждого подмодуля нужно заранее указать ветку в .gitmodules (поле branch). Такой подход делает состояние зависимостей "плавающим", поэтому его обычно используют только в специализированных сценариях (например, для периодической сборки "самых свежих" версий).
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

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