В следующем листинге вывода git status присутствует элемент rack. Если вы выполните git diff для него, то увидите кое-что интересное:
$ git diff --cached rack diff --git a/rack b/rack new file mode 160000 index 0000000..08d709f --- /dev/null +++ b/rack @@ -0,0 +1 @@ +Subproject commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433
Хотя rack является подкаталогом в вашем рабочем каталоге, Git видит его как подмодуль и не отслеживает его содержимое, если вы не находитесь в нём. Вместо этого, Git записывает его как один конкретный коммит из этого репозитория. Если вы производите изменения в этом подкаталоге и делаете коммит, основной проект замечает, что HEAD в подмодуле был изменён, и регистрирует тот хеш коммита, над которым вы в данный момент завершили работу в подмодуле. Таким образом, если кто-то склонирует этот проект, он сможет воссоздать окружение в точности.
Это важная особенность подмодулей – вы запоминаете их как определенный коммит (состояние), в котором они находятся. Вы не можете записать подмодуль под ссылкой master или какой-либо другой символьной ссылкой.
Если вы создадите коммит, то увидите что-то вроде этого:
$ git commit -m 'first commit with submodule rack' [master 0550271] first commit with submodule rack 2 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 .gitmodules create mode 160000 rack
Обратите внимание на режим 160000 для элемента rack. Это специальный режим в Git, который по существу означает, что в качестве записи в каталоге сохраняется коммит, а не подкаталог или файл.
Вы можете расценивать каталог rack как отдельный проект и обновлять ваш основной проект время от времени с указателем на самый последний коммит в данном подпроекте. Все команды Git работают независимо в двух каталогах:
$ git log -1 commit 0550271328a0038865aad6331e620cd7238601bb Author: Scott Chacon <schacon@gmail.com> Date: Thu Apr 9 09:03:56 2009 -0700 first commit with submodule rack $ cd rack/ $ git log -1 commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433 Author: Christian Neukirchen <chneukirchen@gmail.com> Date: Wed Mar 25 14:49:04 2009 +0100 Document version change
Клонирование проекта с подмодулями
Сейчас мы склонируем проект содержащий подмодуль. После получения такого проекта, в вашей копии будут каталоги содержащие подмодули, но пока что без единого файла в них:
$ git clone git://github.com/schacon/myproject.git Initialized empty Git repository in /opt/myproject/.git/ remote: Counting objects: 6, done. remote: Compressing objects: 100% (4/4), done. remote: Total 6 (delta 0), reused 0 (delta 0) Receiving objects: 100% (6/6), done. $ cd myproject $ ls -l total 8 -rw-r--r-- 1 schacon admin 3 Apr 9 09:11 README drwxr-xr-x 2 schacon admin 68 Apr 9 09:11 rack $ ls rack/ $
Каталог rack присутствует, но он пустой. Необходимо выполнить две команды: git submodule init для инициализации вашего локального файла конфигурации, и git submodule update для получения всех данных из подмодуля и перехода к соответствующему коммиту, указанному в вашем основном проекте:
$ git submodule init Submodule 'rack' (git://github.com/chneukirchen/rack.git) registered for path 'rack' $ git submodule update Initialized empty Git repository in /opt/myproject/rack/.git/ remote: Counting objects: 3181, done. remote: Compressing objects: 100% (1534/1534), done. remote: Total 3181 (delta 1951), reused 2623 (delta 1603) Receiving objects: 100% (3181/3181), 675.42 KiB | 173 KiB/s, done. Resolving deltas: 100% (1951/1951), done. Submodule path 'rack': checked out '08d709f78b8c5b0fbeb7821e37fa53e69afcf433'
Теперь ваш подкаталог rack точно в том состоянии, в котором он был, когда вы раньше делали коммит. Если другой разработчик внесёт изменения в код rack и затем сделает коммит, а вы потом обновите эту ссылку и сольёте её, то вы получите что-то странное:
$ git merge origin/master Updating 0550271..85a3eee Fast forward rack | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) [master*]$ git status # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: rack #
Вы слили то, что по существу является изменением указателя на подмодуль. Но при этом обновления кода в каталоге подмодуля не произошло, так что всё выглядит так, как будто вы имеете грязное состояние в своём рабочем каталоге:
$ git diff diff --git a/rack b/rack index 6c5e70b..08d709f 160000 --- a/rack +++ b/rack @@ -1 +1 @@ -Subproject commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0 +Subproject commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433
Это всё из-за того, что ваш указатель на подмодуль не соответствует тому, что на самом деле находится в каталоге подмодуля. Чтобы исправить это, необходимо снова выполнить git submodule update:
$ git submodule update remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 2 (delta 0) Unpacking objects: 100% (3/3), done. From git@github.com:schacon/rack 08d709f..6c5e70b master -> origin/master Submodule path 'rack': checked out '6c5e70b984a60b3cecd395edd5b48a7575bf58e0'
Вы вынуждены делать так каждый раз, когда вы получаете изменения подмодуля в главном проекте. Это странно, но это работает.
Распространённая проблема возникает, когда разработчик делает изменения в своей локальной копии подмодуля, но не отправляет их на общий сервер. Затем он создаёт коммит содержащий указатель на это непубличное состояние и отправляет его в основной проект. Когда другие разработчики пытаются выполнить git submodule update, система работы с подмодулями не может найти указанный коммит, потому что он существует только в системе первого разработчика. Если такое случится, вы увидите ошибку вроде этой: