[Подмодули] кажутся излишне сложными ...
Вероятно, правда. Однако субмодули также обязательно сложны. :-) Также отмечу, что поддержка субмодулей в Git 2.x заметно лучше, чем в старые добрые времена Git 1.5 или 1.6 или около того, когда я узнал, почему люди называли их sob -модулями. Часть этой истории, вероятно, объясняет, почему здесь есть некоторая сложность.
Прежде чем перейти к более длинному ответу, вот краткий способ начать: используйте git clone --recurse-submodules
или выполните git submodule update --init --recursive
сразу после клонирования. (Второй --recursive
требуется только в том случае, если у подмодуля есть собственные подмодули.) Добавление опции --recurse-submodules
в git clone
просто говорит git clone
, что нужно сделать это git submodule update --init --recursive
после обычной последовательности операций. Обратите внимание, что не поможет вам с процессом , работающим в подмодулях.
Long
Как мне ...
Git - это инструмент, а не решение (общепринятая поговорка в строительном бизнесе , очевидно, но в целом применима к большинству технологий). Как и в большинстве инструментов, их можно использовать несколькими способами.
Что нужно знать о подмодуле, так это то, что каждый подмодуль - это просто еще один репозиторий Git. Единственное, что делает репозиторий Git «подмодулем», это тот факт, что существует некоторый «внешний более поздний» репозиторий, который каким-то образом контролирует внутренний репозиторий. Из внутреннего хранилища мы называем внешний как суперпроект .
В любом репозитории Git, в котором вы будете выполнять какую-либо работу, у вас есть рабочее дерево . Рабочее дерево хранит файлы в их обычной повседневной форме, где вы (и другие программы на вашем компьютере) можете работать с ними. Каждый Git-репозиторий также имеет индекс , в котором вы создаете следующий коммит, который сделаете. Индекс также называют промежуточной областью , а иногда кешем , что отражает либо его чрезвычайно важные роли, либо неудачный выбор слова «индекс» для его исходного имени (или, возможно, обоих ). И, конечно же, каждый репозиторий Git имеет коллекцию коммитов с различными именами ветвей и / или именами тегов , которые идентифицируют конкретные хэши фиксации с помощью некоторого вида человекочитаемое имя.
Если бы этот репозиторий Git стоял сам по себе, эти имена - имена веток и тегов - были бы полезны для нас, людей, выполняющих работу в этом репозитории. Но мы только что объявили, что этот репозиторий является подмодулем , который живет (или умирает) по команде какого-то другого репозитория - суперпроекта . Наши собственные имена веток и тегов практически бесполезны. Они становятся полезными, если и когда мы рассматриваем этот репозиторий как обычный репозиторий, а не просто как дополнение к какому-либо суперпроекту. Когда мы рассматриваем этот репозиторий как контролируемую сущность, мы хотим, чтобы этот репозиторий имел вместо отсоединенный HEAD Суперпроект, а не подчиненный подмодуль, определяет хэш коммита для проверки не по какому-нибудь удобочитаемому имени, а по необработанному хеш-идентификатору.
Это относится ко всем ответам "как я". суперпроект записывает в индексе суперпроекта по его необработанному хэш-идентификатору конкретную фиксацию, что должен быть извлечен в подмодуле.
Клонирование
[Как мне] клонировать репо ... так, чтобы [клон] был полностью заполнен ... всеми извлеченными подмодулями?
Как и любой клон, это можно сделать с помощью git clone <em>url [dir]</em>
, который на самом деле состоит из шести шагов:
- Создайте новый пустой каталог
dir
и переключитесь на него (cd
) или используйте какой-нибудь существующий пустой каталог, если указано: ([ -d <em>dir</em> ] || mkdir <em>dir</em>) && cd <em>dir</em>
. (Если это не удастся, остановитесь, не делайте ни одного из оставшихся шагов. Если последующий шаг не удался, удалите новый каталог, если мы его создали, и удалите весь файл, который мы сделали, не оставляя следов частично неудачного клона.) Если мы не дадим git clone
имя каталога, оно вычисляется из аргумента url
.
- Создать новый пустой репозиторий:
git init
. Это создаст каталог .git
и начальную конфигурацию.
- Выполнить любую необходимую дополнительную настройку из
-c
параметров, указанных после git clone
.
- Добавьте remote , получив url:
git remote add <em>remote url</em>
. Обычное имя для пульта - origin
, но вы можете управлять этим с помощью опции -o
.
- Получить коммиты с пульта:
git fetch <em>remote</em>
.
- Проверьте название какой-либо ветви или тега:
git checkout <em>name</em>
. Если это имя ветви, то ветвь еще не существует, поэтому создает ветвь так же, как git checkout
. Если это имя тега, это проверяет фиксацию как отдельный HEAD. name
здесь тот, который вы дали с опцией -b
. Если вы его не указали, его имя можно узнать, спросив у Git на другом конце операции git fetch
, какую ветку рекомендует , что довольно часто master
. Если это также не сработает - если у другого Git нет имени для рекомендации - используется имя master
.
На последнем шаге, шаг 6, проверяется некоторая конкретная фиксация, обычно путем «включения» ветви, такой как master
, , создавая это имя ветви на основе имен, полученных на шаге 5 (git fetch
, что составило origin/master
). Акт проверки этой конкретной фиксации заполняет индекс и рабочее дерево репозитория, так что теперь у вас есть в своем рабочем дереве все необходимые файлы.
Подмодули и gitlinks
Если зафиксированный вами коммит имеет подмодули, он имеет файл с именем .gitmodules
и в этом коммите, который вы только что извлекли, одну или несколько специальных записей, каждая из которых называется gitlink . Запись gitlink очень похожа на запись файла (blob
) или запись tree
, но имеет код типа 160000
, а не 100644
(обычный файл) или 100755
(исполняемый файл) или 004000
(дерево). 1 Эти записи gitlink попадают в ваш индекс, и ваш Git создает пустой каталог по пути, указанному gitlink, так же, как ваш Git создает подкаталог для tree
или файл для blob
. 2 Хеш-идентификатор , связанный с этими записями gitlink - каждая запись индекса имеет хеш-идентификатор, - это один конкретный коммит в субмодуль, который Git может, но пока не может, проверить как отдельный заголовок.
Обратите внимание, что я сказал здесь , если commit , который вы только что извлекли, имеет подмодули . Это еще одна ключевая реализация: «субмодуль» подмодуля контролируется специфическим коммитом в суперпроекте. Этот коммит должен иметь запись gitlink, чтобы дать хэш-идентификатор для извлечения в подмодуле, и файл .gitmodules
. Но для чего этот файл .gitmodules
?
1 Для символических ссылок есть еще один индексный код типа 120000
. Они обрабатываются почти точно так же, как и объекты blob
, за исключением того, что, пока включены символические ссылки, Git записывает содержимое как символическую ссылку, а не как файл. Если символические ссылки отключены, Git записывает содержимое как обычный файл, так что вы можете отредактировать его и позже добавить его как символическую ссылку, используя git update-index
, если вы знаете всю магию работы с записями индекса.
2 Тот факт, что Git создаст пустой каталог для объекта tree
, побудил людей попытаться использовать полусекретное пустое дерево Git 1187 * для хранения пустых каталогов. К сожалению, сам индекс имеет здесь странные угловые случаи, и Git превращает пустое дерево в запись gitlink
при различных условиях.Это тогда действует как сломанный подмодуль - gitlink без записи .gitmodules
- что заставляет Git вести себя немного плохо.
Файл .gitmodules
Мы только что виделивыше, что git clone
нужен хотя бы один аргумент: url для клонирования хранилища.Суперпроект хранит желаемый идентификатор хэша коммита в gitlink, но как он узнает, какой url использовать?Ответ - посмотреть в файле .gitmodules
.
Содержимое .gitmodules
отформатировано так же, как .git/config
или $HOME/.gitconfig
или любой другой файл конфигурации Git, и фактически Gitиспользует git config
для их чтения:
git config -f .gitmodules --get submodule.path/to/x.url
Это ищет
[submodule "path/to/x"]
url = <whatever you put here>
в файле .gitmodules
, и когда мы его находим, , который предоставляетURL.
Фактически содержимое будет:
[submodule "path/to/x"]
path = path/to/x
url = <whatever you put here>
и, возможно, также один или оба из:
branch = <name>
update = <control>
path
должно соответствоватьотносительный путь подмодуля в суперпроекте, а имя подмодуля должно быть относительным путем подмодуля в суперпроекте.(Что произойдет, если один или другой из них неправильный / не совпадают, я не совсем уверен. Команды субмодуля Git обычно проверяют соответствие, так что вопрос никогда не возникает.)
Это позволяетgit submodule
найти URL, чтобы сделать клон. Этот процесс сложен. Когда вы запускаете git submodule init
или git submodule update --init
, Git копирует значение url
с .gitmodules
до .git/config
.Если есть параметр update = <em>control</em>
, он также скопирует его, если в .git/config
уже есть параметр.(Это одно из тех «ненужных осложнений», о которых вы упомянули, хотя я думаю, что оно исправляет исторические ошибки.)
Без --init
команда git submodule update
будет просматривать только записи в .git/config
не те, что в .gitmodules
.Это означает, что вы можете использовать двухступенчатую последовательность git submodule init && git submodule update
, чтобы сделать то же самое, но git submodule update --init
легче ввести.Что еще более важно, git submodule init
не имеет опции --recursive
, а git submodule update
.Это на самом деле разумно, поскольку git submodule init
только копирует с .gitmodules
до .git/config
(подробнее об этом см. Ниже).Операция git submodule update
фактически создает клон, используя описанный выше шестиэтапный процесс.
Отсоединение HEAD от правильного коммита в подмодуле
Мы видели, что суперпроект перечисляет правильный идентификатор хеша для подмодуля, как запись gitlink.Это означает, что Git нужно запустить в суперпроекте, прочитать запись gitlink из индекса, затем переключиться на подмодуль (cd <em>path</em>
) и git checkout
правильный коммит по его хеш-идентификатору.Это приведет к отключению HEAD с проверкой правильной фиксации.
Команда, которая делает это, git submodule update
.И это, как правило, то, что мы хотим: проверить этот конкретный коммит по его хеш-идентификатору в виде отдельного HEAD.Теперь, когда мы получили то, что хотим в подмодуле, мы закончили ... или мы мы?Что если этот Git-репозиторий - помните, что каждый подмодуль является обычным Git-репозиторием сам по себе - что, если этот Git-репозиторий имеет собственные подмодули?
Подмодули могут иметь подмодули
Если этотподмодуль имеет свои собственные подмодули, теперь мы хотим, чтобы этот суб-Git git checkout
выполнил правильный коммит, запустил git submodule init
, чтобы инициализировать .git/config
для своих подмодулей, и запустил git submodule update
, чтобы его собственные подмодули были извлеченыправильный коммит.Это именно то, что git submodule update
уже делает от имени нашего суперпроекта, поэтому мы просто хотим, чтобы эта git submodule update
до рекурсивно работала с подмодулями подмодуля. Это означает, что git submodule update
должна быть в состоянии вернуться вподмодули, а также --init
их.
Так это , почему существует git submodule update --init --recursive
: это рабочая лошадка, которая входит в каждый подмодуль из суперпроекта, устанавливает его .git/config
, если необходимо, проверяет правильный хеш отсоединенного HEAD, а затем повторяется в подмодулях подмодуль.
git clone
может вызвать git submodule update
Если теперь мы перемотаем весь путь обратно к git clone
, мы увидим, что после шага 6 нам нужен шаг 7: git submodule update --init --recursive
, чтобы перейти в каждый подмодуль, указанный в суперпроекте, инициализировать его и проверить правильный отделенный заголовок, и если этот подмодуль является суперпроектом дополнительных подмодулей, обрабатывать их рекурсивно. В конце концов, у нас будет суперпроект с его конкретным коммитом, который будет контролировать все свои подмодули, которые находятся на правильном коммите как отдельный HEAD, а для каждого из этих подмодулей, который сам является суперпроектом с подмодулями, субмодуль-as Коммит -superproject будет рекурсивно управлять подмодулями подмодуля-суперпроекта.
Если у вас нет рекурсивных подмодулей, все рекурсии заканчиваются бездействием: это немного дополнительной работы, но безвредно. Так что обычно это путь: просто запустите git clone --recurse-submodules
, и вы получите клон, созданный с его подмодулями, как отдельные хранилища HEAD, и все готово.
Работа в подмодулях
У вас был почти отдельный вопрос:
Как мне обновить файл в другом / субмодуле?
Мы видели выше, что способ, которым суперпроект управляет / использует подмодуль, заключается в том, что суперпроект задает с помощью абсолютного идентификатора хеша, который фиксирует, что подмодуль должен быть заблокирован как отдельный HEAD. Это отлично подходит для управления и использования подмодуля, за исключением случаев, когда мы хотим обновить подмодуль до более новой фиксации.
Традиционный ответ, начиная с 1,5 дней Git, заключается в том, что, поскольку подмодуль является репозиторием Git, просто cd
в подмодуль и git checkout <branchname>
и начинайте работать. Это все еще работает! Однако у него есть очевидный недостаток: откуда вы знаете, какое имя ветви использовать?
В некоторых случаях вы просто знаете. Все в порядке; иди и используй их таким образом. Если вы хотите, чтобы суперпроект знал, однако, это то, где приходит настройка branch =
суперпроекта, и где приходят аргументы для git submodule update
и / или настроек submodule.<em>name</em>.update
(также в суперпроекте) in. Помните, что эти настройки взяты из файла .git/config
в суперпроекте, а не из самого подмодуля, и (обычно 3 ) не из файла .gitmodules
, а из содержимого .gitmodules
установить настройки по умолчанию .git/config
. Таким образом, существует множество способов управления этой конфигурацией.
Далее возникает вопрос о том, что каждая конфигурация делает , и как вы хотите настроить ее для своих собственных целей. Они перечислены (довольно плохо, на мой взгляд) в git submodule
документации . Вот мое собственное резюме их резюме с дополнительными комментариями.
checkout
: фиксация, записанная в суперпроекте, будет проверена в субмодуле на отдельном HEAD.
Это значение по умолчанию и это то, что мы видели выше.
rebase
: текущая ветвь подмодуля будет переназначена на коммит, записанный в суперпроекте.
Это бесполезно, если вы уже не вошли в подмодуль и не сделали что-то там. Однако есть также опция --remote
, описанная ниже в документации, что делает ее более полезной.
merge
: фиксация, записанная в суперпроекте, будет объединена с текущей веткой в субмодуле.
Как и в случае rebase
, это само по себе бесполезно: вам нужно либо --remote
, либо выполнить свою собственную работу в подмодуле, прежде чем делать это.
пользовательская команда: выполняется произвольная команда оболочки, которая принимает один аргумент (sha1 коммита, записанного в суперпроекте).
Этот сам по себе полезен, но требует, чтобы вы выполнили некоторую предварительную работу в суперпроекте, чтобы настроить конфигурацию и определить команду.
none
: подмодуль не обновляется.
Это в первую очередь полезно для отметки подмодуля, который не обновляется, когда все остальные подмодули этого конкретного суперпроекта делают. Если у вас есть только один субмодуль, этот параметр вообще не работает.
До сих пор мы не видели никакого применения для настройки branch
, скопированной с .gitmodules
на .git/config
. Этот параметр --remote
, описанный ниже в той же документации, говорит о том, как используется этот параметр:
... Вместо того, чтобы использовать записанный SHA-1 суперпроекта для обновления подмодуля, используйте статус ветви удаленного отслеживания подмодуля.
То есть, у суперпроекта есть запись gitlink, которая говорит использовать хеш a1b2c3d ... или что-то еще, но вместо использования этого хеша, когда команда суперпроекта git submodule update
работает с репозиторием Git удерживая субмодуль, команда суперпроекта будет искать, например, origin/master
в субмодуле. Название master
здесь происходит от этой настройки ветви, поэтому установка submodule.<em>name</em>.branch
, скажем, develop
вместо этого заставит суперпроект использовать origin/develop
вместо origin/master
. 4
Чтобы сделать это полезным, суперпроект Git запускает git fetch
в подмодуле, прежде чем запускать что-либо из этого. Это заставляет подмодуль переносить любые новые коммиты из его origin
Git, обновляя его origin/master
, origin/develop
и так далее. Здесь предполагается, что вы не выполняли никакой работы в подмодуле самостоятельно! Вы просто захватываете работу, которую кто-то другой выполнил в хранилище origin
, из которого клонировано хранилище подмодулей (вот так!).
3 Настройка в .gitmodules
будет использоваться, если в .git/config
нет настройки и нет переопределения в командной строке. Я думаю, что это еще один элемент обратной совместимости.
4 Предполагается, что origin/develop
- это имя удаленного слежения, связанное с веткой develop
в хранилище подмодулей, т. Е. Все настроено как обычно.
Подготовка обновленного подмодуля
Если вы собираетесь выполнять свою собственную работу в своем собственном подмодуле, вам все это не поможет. Вместо этого вам просто нужно cd
войти в хранилище подмодулей и запустить git checkout <em>branchname</em>
. Это снимет вас с вашего отдельного ГОЛОВА и поместит вас в данную ветку, и теперь вы можете работать как обычно. Напишите код git add
и git commit
как обычно. Когда все будет готово в субмодуле, cd
вернитесь к суперпроекту. У вас будет субмодуль на ветке (не в режиме отключенного HEAD), на каком-то конкретном коммите.
Если вы просто берете чужую работу , это git submodule update --remote --checkout
или что угодно git fetch
, а затем git checkout origin/master
или что-то, в зависимости от случая, в подмодуле. Это оставит ваш подмодуль в ветке no , в режиме отсоединенного HEAD, на каком-то конкретном коммите. Это скорее всего то, что вы хотите.
Использование обновленного субмодуля в суперпроекте
В любом случае, с точки зрения суперпроекта , произошло то, что субмодуль теперь находится на другом коммите. Суперпроект не заботится , подключен или отключен заголовок подмодуля; имеет значение текущий коммит в подмодуле.
Теперь, когда субмодуль находится на желаемом коммите, внесите любые другие изменения в суперпроект, которые вы хотите - возможно, есть какой-то файл, который должен использовать, например, некоторую новую функцию субмодуля. Когда вы закончите вносить необходимые изменения, git add
все обновленные файлы, а также запустите git add
по пути субмодуля (без завершающего слеша):
git add features.ext # updated to use feature F of submodule sub/S
git add sub/S # record the new gitlink for sub/S!
Это обновляет индекс суперпроекта, так что теперь у нас есть не только обновленный файл (features.ext
), но и новый правильный хеш-идентификатор для подмодуля - обновленная gitlink. Теперь мы можем запустить git commit
в суперпроекте как обычно:
git commit
и это делает наш новый коммит, который имеет gitlink, который записывает тот факт, что подмодуль sub/S
должен быть извлечен с отсоединенным HEAD при коммите f37c219...
или чем бы ни был текущий коммит sub/S
. Этот новый коммит идет в любой ветви, которую мы отметили в суперпроекте, будь то master
или develop
или что-то еще.
Нажатие
Допустим, мы проделали собственную работу в sub/S
, в его ветке devel
, создав коммит f37c219...
. Затем мы сделали наш новый коммит в нашем суперпроекте на суперпроекте master
; по какой-то странной случайности его хэш-идентификатор равен abcdef1...
. Теперь, когда у нас есть два репозитория с обновлениями, мы можем git push
их. Но есть ограничение порядка!
Предположим, мы сейчас выдвигаем наш суперпроект:
git push origin master
Наш новый коммит abcdef1
отправляется в наш апстрим-репозиторий, а , который Git's master
теперь называет наш новый коммит abcdef1
. Наш новый коммит говорит, что субмодуль sub/S
должен быть проверен на коммите f37c219
. Таким образом, Фред на компьютере Фреда запускает git clone
или git fetch
или что-то еще, и получает наш commit abcdef1
, который говорит: "используйте commit f37c219...
при использовании sub / S". Фред бежит git submodule update
, и его Git входит в его sub/S
и пытается проверить f37c219
, и, к сожалению, Фред не имеет f37c219
. На самом деле, только у нас есть f37c219
, потому что мы только что сделали это!
Лучше бы нам очень быстро cd sub/S
и запустить git push origin develop
. (Помните, что мы сделали f37c219
на нашем develop
в нашем подмодуле.) Таким образом, когда Фред пытается получить доступ к f37c219
, он, по крайней мере, доступен где-то . Лучше, если мы git push
этот сначала , , а затем git push origin master
в суперпроекте нажмем abcdef1
, что относится к f37c219
. Таким образом, это приводит к обновлению правила № 2: сначала подталкивают субмодули в порядке наиболее глубоких субмодулей. Таким образом, каждый суперпроект ссылается на коммит, к которому Фред или кто угодно может получить.
Есть еще одна незначительная болевая точка для Фреда
Мы представили вышеупомянутого Фреда как первого парня, который извлекает (и объединяет, перебазирует или иным образом включает, возможно, даже git pull
) наш коммит суперпроекта, который ссылается на новые коммиты подпроекта. Тем не менее, Фред здесь означает любого , который клонировал наш суперпроект. У них у всех есть наш суперпроект, и они все запускали git submodule update --init --recursive
, возможно, как часть самой команды клона, которая дала им суперпроект, поэтому у них уже есть все подмодули.
Но у них нет новых коммитов в подмодулях . Когда они обновят свой суперпроект и сообщат своему Git git submodule update
, их Git войдут в свои подмодули и не найдут правильные хеши фиксации . К счастью, git submodule update
достаточно умен, чтобы запустить git fetch
для вас (или для Фреда).
Однако, чтобы это работало, кто бы ни обновлялся, он должен быть в сети. Это означает, что вы должны запустить git submodule update
при подключении. Если вы всегда на связи, это не проблема, но если нет, то должен быть простой способ получить все подмодули заранее.
Там нет git submodule fetch
, но есть команда, которая сделает трюк:
git submodule foreach --recursive git fetch
Это запустит git fetch
в каждом подмодуле, чтобы обновить его. Таким образом, более поздняя версия git submodule update
, используемая с любым коммитом в суперпроекте, будет работать, даже если вы находитесь в автономном режиме, а подмодулям потребуется обновление.