Подмодули

Большие проекты часто составлены из маленьких, самодостаточных модулей. Например, дерево исходных кодов дистрибутива "Встроенный Linux" включает каждую часть програмного обеспечения обычного дистрибутива с некоторыми изменениями; сборка видео проигрывателя возможно потребует только определенную, рабочую версию библиотеки декомпресии; несколько независимых программ возможно пользуются совместно одним наборов скриптов для сборки.

С централизованной системой контроля версий это часто достигается включением каждого модуля в единый репозиторий. Разработчики могут извлекать все модули или только модули необходимые им для работы в данный момент. Они могут даже формлять модифицированные файлы в нескольких модулях в единый коммит, в то время как вокруг перемещаются файлы или обновляется API и переводы..

Git не позволяет выполнять частичное извлечение, и дублирование этого подхода в Git заставит разработчиков поддерживать локальные копии модулей которые они не трогают. Коммиты в таких огромных репозитория будут намного медленнее чем вы ожидаете, так как Git должен будет просканировать каждую директорию на наличие изменений. Если модули имеют длительную локальную историю то клонирование может занять вечность.

Распределенные системы контроля версий имеют одну положительную сторону, они могут быть гораздо легче интегрированы с внешними источниками. В централизованной модели, один произвольный снапшот внешнего проекта экспортирован из его собственной системы контроля версий и затем импортирован в локальную систему контроля версий в ветку vendor. Вся история скрыта. С распределенной системой контроля версий вы модете склонировать всю внешнуюю историю и даже больше просто следуя разработке и пере-слиянию локальных изменений.

Поддержка в Git подмодулей позволяет репозиторию содержать, как поддиректорию, извлеченный внешний проект. Подмодули поддерживают свою подлинность; поддержка подмодулей хранит расположение репозитория подмодуля и ID коммита, так что другие разработчики которые клонируют существующий проект ("superproject")могут легко клонировать все подмодули при извлечении. Частичные извлечения superproject возможны: вам можете указать Git клонировать ничего, часть или все подмодули.

Команда git submodule доступна начиная с версии Git 1.5.3. Пользователи с Git 1.5.2 могут поискать коммиты подмодулей в репозитории и вручную извлечь их; ранние версии Git не узнают подмодули вообще..

Чтобы увидеть как работает поддержка подмодулей, создайте (для примера) 4 образца репозиториев которые могут быть использованы позже как подмодуль:

$ mkdir ~/git
$ cd ~/git
$ for i in a b c d
do
    mkdir $i
    cd $i
    git init
    echo "module $i" > $i.txt
    git add $i.txt
    git commit -m "Initial commit, submodule $i"
    cd ..
done

Теперь создайте superproject и добавте все подмодули:

$ mkdir super
$ cd super
$ git init
$ for i in a b c d
do
    git submodule add ~/git/$i $i
done

Замечание: Не используйте здесь локальные URL если вы планируете опубликовать ваш superproject!

Посмотрим какие файлы создал git-submodule:

$ ls -a
.  ..  .git  .gitmodules  a  b  c  d

Команда git-submodule add выполняет пару вещей:

Выполним коммит superproject:

$ git commit -m "Add submodules a, b, c and d."

Теперь склонируем superproject:

$ cd ..
$ git clone super cloned
$ cd cloned

Директории подмодуля здесь, но они пустые:

$ ls -a a
.  ..
$ git submodule status
-d266b9873ad50488163457f025db7cdd9683d88b a
-e81d457da15309b4fef4249aba9b50187999670d b
-c1536a972b9affea0f16e0680ba87332dc059146 c
-d96249ff5d57de5de093e6baff9e0aafa5276a74 d

Замечание: Имена объектов коммит показанные выше будут отличаться от ваших, но они должны совпадать с именами объектов коммит HEAD ваших репозиториев. Вы можете проверить это выполнив git ls-remote ../git/a.

Снос подмодулей это двух шаговый процесс. Сначала выполните git submodule init чтобы добавить URL репозитория подмодуля в .git/config:

$ git submodule init

Теперь используйте git-submodule update чтобы склонировать репозитории и извлечь коммиты определенные в superproject:

$ git submodule update
$ cd a
$ ls -a
.  ..  .git  a.txt

Одна существенная разница между git-submodule update и git-submodule add это то что git-submodule update извлекает определенный коммит, чем кончик ветки. Это как извлечение тага: голова отделена, так что вы не работает на ветке.

$ git branch
* (no branch)
master

Если вы хотите сделать изменения внутри подмодуля и у вас есть отсоединенная голова, тогда вы должны создать или извлечь ветку, сделать изменения, опубликовать изменения внутри подмодуля, и затем обновить superproject чтобы он указывал на новый коммит:

$ git checkout master

или

$ git checkout -b fix-up

затем

$ echo "adding a line again" >> a.txt
$ git commit -a -m "Updated the submodule from within the superproject."
$ git push
$ cd ..
$ git diff
diff --git a/a b/a
index d266b98..261dfac 160000
--- a/a
+++ b/a
@@ -1 +1 @@
-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b
+Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24
$ git add a
$ git commit -m "Updated submodule a."
$ git push

Вы должны выполнить git submodule update после git pull если вам также нужно обновить подмодули.

Заблуждения с подмодулями

Всегда побликуйте изменения подмодуля перед тем как публиковать изменения в superproject который ссылается на него. Если вы забыли опубликовать изменения подмодуля, другие не смогут склонировать репозиторий:

$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a
$ git commit -m "Updated submodule a again."
$ git push
$ cd ~/git/cloned
$ git pull
$ git submodule update
error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git.
Did you forget to 'git add'?
Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'

Если вы индексирете обновленный подмодель для ручного коммита, будьте осторожны не добавляйте в конце слэш когда определяете путь. Если слэш присутсвует в конце, Git будет полагать что вы удаляете подмодуль и проверяете содержимое этой дироктории в содержащий репозиторий.

$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a/
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       deleted:    a
#       new file:   a/a.txt
#
# Modified submodules:
#
# * a aa5c351...0000000 (1):
#   < Initial commit, submodule a
#

Чтобы исправить индекс после выполнения этой операции, откатите изменения и затем добавте подмодуль без слэша в конце пути..

$ git reset HEAD A
$ git add a
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   a
#
# Modified submodules:
#
# * a aa5c351...8d3ba36 (1):
#   > doing it wrong this time
#

Вы также не должны перематывать ветки в подмодуле кроме коммитов которые были записаны в любом суперпроекте.

Это не безопасно выполнять git submodule update если вы сделали и закоммитили изменения в подмодуле без извлечения первоначально ветки. Они будут молча перезаписаны.:

$ cat a.txt
module a
$ echo line added from private2 >> a.txt
$ git commit -a -m "line added inside private2"
$ cd ..
$ git submodule update
Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'
$ cd a
$ cat a.txt
module a

ЗАМЕЧАНИЕ: Изменения все еще видимы в reflog подмодуля.

Это не тот случай если вы не выполнили коммит для ваших изменений.

gitcast:c11-git-submodules



github logo