TL; DR: здесь, наверное, нет ничего плохого, кроме сварливого рецензента. :-) Вы можете сделать его счастливее, если вы немного измените свой git add
процесс или возитесь со своим пиар-зданием, но, возможно, было бы лучше сделать его менее раздражительным в отношении обновлений или вообще не использовать подмодули (но оба они - больше предмет обсуждения команды). Вы можете перейти к разделу процесса обновления субмодуля, если большая часть длинного раздела ниже знакома.
Long
Подмодули в основном немного клунки, и вокруг них всегда будут проблемы.
Во-первых, давайте просто обратимся к этим:
субмодуль постоянно находится в состоянии отсоединенной головы
Это нормально. Отсоединенный HEAD означает просто Я не в какой-либо ветке, вместо этого у меня зафиксирован определенный коммит, , и субмодуль обычно не находится ни в одной из веток, подобных этой.
status
родительского элемента остается чистым, без изменений в подмодуле
Это тоже достаточно нормально (подробности мы увидим чуть позже). Ни один из них не связан (по крайней мере, напрямую) с вашей проблемой PR.
Подмодуль является собственным Git-репозиторием
Теперь давайте обратимся к этой части:
есть ежедневный процесс получения обновлений, который происходит git submodule update --remote
Если мы посмотрим документацию git submodule
, мы обнаружим, что эта подкоманда задокументирована (не очень хорошо) в разделе под опцией --remote
:
Эта опция действительна только для команды update
. Вместо того, чтобы использовать
записанный SHA-1 суперпроекта для обновления субмодуля, используйте
состояние ветки удаленного слежения подмодуля. Пульт используется
является удаленным филиалом (branch.<name>.remote
), по умолчанию origin
.
Удаленная ветвь по умолчанию использует master
, но имя ветки может
переопределить, установив параметр submodule.<name>.branch
в
или .gitmodules
или .git/config
(с .git/config
взятием
преимущество).
Это работает для любой из поддерживаемых процедур обновления ... Единственное изменение - это источник целевого SHA-1 ... [snip]
Здесь есть что распаковать. Давайте начнем с самого простого: подмодуль - это Git-репозиторий сам по себе, с именами веток, именами тегов, HEAD
, индексом, рабочим деревом и так далее. Следовательно, есть два репозитория Git: один для суперпроекта (который перечисляет субмодуль в качестве репозитория для использования), и один для самого подмодуля. Подмодуль не содержит в себе ничего особенного. Единственными специальными элементами являются те, которые являются следствием того, что суперпроект Git выполняет некоторую работу, прежде чем выполнять какое-либо клонирование или git checkout
-ing:
- каталог
.git
(или $GIT_DIR
), содержащий фактический подмодуль, обычно находится в каталоге .git
суперпроекта; 1
- рабочее дерево подмодуля живет по пути, определенному суперпроектом; и
- коммит , который суперпроект Git проверяет и / или считает правильным, поскольку подмодуль - обычно так или иначе - определяется хеш-идентификатором, записанным в суперпроекте .
Этот последний элемент является источником отсоединенного заголовка. Шаг, который извлекает подмодуль в свое рабочее дерево, запускается git checkout <em>hash-ID</em>
, откуда изначально * hash-ID
суперпроект коммит. При запуске git submodule update --remote
суперпроект Git сообщает подпроекту Git:
- сначала запустите
git fetch
, чтобы мы могли увидеть, есть ли новый хеш коммита для вашей ветви;
- тогда, если - это новый хеш коммита, запустите
git checkout <em>hash</em>
, чтобы переключиться на него.
ЭтоКонечно, также приводит к отсоединению HEAD. Самая любопытная часть - это шаг, описанный как , посмотрите, есть ли новый хеш коммита для вашей ветки , потому что подмодуль не находится на ветке! Вместо этого он имеет отсоединенный HEAD. Если вы говорите «WTF» себе здесь, вы на правильном пути (не каламбур). Последний абзац под --remote
имеет ответ:
... update --remote
использует ... submodule.<name>.branch
[чтобы выяснить имя ветви для подмодуля и, следовательно, есть ли новый хеш коммита, и если это так, передает его в git checkout
.]
(Вы можете update --remote
использовать любой из checkout, merge или rebase. При использовании последних двух это более сложно. Нам не нужно больше никаких сложностей, поэтому давайте придерживаться случая checkout
.)
1 Эта функция была новой в Git 2.12, когда был добавлен «Abshibgitdirs». Ранее .git
для подмодуля находилось в корне рабочего дерева подмодуля. Теперь происходит то, что подмодуль Git записывает файл с именем .git
в корень своего рабочего дерева. Файл .git
указывает подмодулю Git заглянуть в каталог .git
суперпроекта, чтобы он мог видеть, что это подмодуль суперпроекта.
Суперпроект фиксирует хеш-идентификаторы субмодуля записи
Существует общее правило коммитов в репозиториях Git, которое применяется ко всем репозиториям и всем коммитам: все они являются полными снимками всего. Это верно для хранилища субмодулей - каждый коммит представляет собой полный снимок всех файлов, а также для суперпроекта. Однако вместо записи файлов субмодуля суперпроект фиксирует запись хеш-идентификатора подмодуля .
Механизм, стоящий за этим, - Git's index . За исключением репозиториев --bare
(у них есть нет рабочего дерева), Git-репозиторий поставляется с одним индексом и одним рабочим деревом. Индекс содержит копию каждого файла, который вышел из текущего коммита и который войдет в следующий коммит, который вы делаете.
Сохраненные файлы в репозитории, записанные каждым коммитом, хранятся в специальном сжатом (иногда сильно сжатом) формате Git-only. Находясь в коммите, эти файлы также полностью доступны только для чтения, что означает, что новый коммит может просто повторно использовать старый файл из старого коммита, если вы его не изменили. Это большая причина, почему, хотя каждый коммит хранит каждый файл, репозиторий не растет быстро: новые коммиты на самом деле просто повторно используют файлы старых коммитов.
Конечно, файлы, доступные только для чтения, не могут быть изменены, а файлы в форме только для Git не могут использоваться ничем, кроме Git. Таким образом, Git должен расширить эти файлы только для чтения, Git-only, которые Git хранит с коммитами, в файлы обычного формата для чтения / записи, которые вы можете использовать. Эти файлы обычного формата для чтения / записи попадают в ваше рабочее дерево.
Большинство систем контроля версий на этом останавливаются: в хранилище есть постоянные, только для чтения, замороженные сжатые файлы и временные файлы для чтения / записи в вашем рабочем дереве, с которыми вы работаете. Чтобы сделать новый коммит, VCS снова сжимает каждый файл рабочего дерева и проверяет, находится ли он уже в репозитории. Если это так, он повторно использует старый; если нет, он вставляет новый; В любом случае новый коммит ссылается на новый файл, даже если это просто старый файл. Но это ужасно медленно.
Вместо этого Git размораживает, но сохраняет как сжатый и только для Git, каждый файл из текущего коммита .Те идут в индекс.Затем Git заставляет вас , программиста, git add
каждый файл, когда вы изменяете его: это повторно сжимает файл в формат Git-only и копирует его в индекс, перезаписывая предыдущую копию индекса, еслибыл один, или создание файла с нуля, если он полностью новый.В любом случае, индекс готов к работе , чтобы сделать новый коммит, поэтому git commit
невероятно быстро: ему просто нужно заморозить все уже подготовленные файлы.
Этопочему индекс можно описать как следующий коммит, который вы сделаете, если вы запустите git commit
прямо сейчас .(У него также есть несколько других полезных функций. Так что тот факт, что Git имеет индекс на вашем лице, заставляя вас все время в него вставать git add
, может раздражать, но также может быть полезным. Но этот аспект - index = следующий коммит - ключевой.)
Это хорошо для файлов, но как насчет подмодулей?Итак, информация о субмодуле, которая находится в суперпроекте commit , является хеш-идентификатором, который подмодуль должен git checkout
.Так что Git сохраняет это в коммите и в индексе.В следующем коммите, который вы сделаете, будет содержаться этот хеш подмодуля.
Процесс обновления подмодуля
Первоначальная проверка (например, git submodule update --init
) просто проверяетконкретный коммит, как мы видели ранее.Это помещает правильный коммит в подмодуль, а также имеет правильный коммит хэш в индексе суперпроекта:
Submodule path 'sub': checked out '8ffac73422c73898facacb7a0f92ed15a29cc7ad'
Мой подмодуль Git теперь находится в отсоединенном состоянии HEAD.Коммит HEAD
в моем суперпроекте показывает, что правильный коммит подмодуля равен 8ffac73422c73898facacb7a0f92ed15a29cc7ad
, и индекс говорит, что использовать этот коммит: 2
$ git rev-parse HEAD:sub
8ffac73422c73898facacb7a0f92ed15a29cc7ad
$ git rev-parse :0:sub
8ffac73422c73898facacb7a0f92ed15a29cc7ad
Когда вы запускаете git submodule update --remote
и это проверяет какой-то новый коммит, что не записывает новый хеш-код подмодуля, он просто проверяет его.Здесь я обновил удаленный репозиторий подмодуля, так что git submodule update --remote
находит новый хэш-идентификатор для master
в подмодуле (есть только одна ветвь подмодуля, поэтому все автоматически master
):
$ git submodule update --remote
Submodule path 'sub': checked out 'ca09e95a23e28ef71765113ea0caef2bd7ce9594'
Теперь подмодуль находится в этом коммите:
$ (cd sub; git rev-parse HEAD)
ca09e95a23e28ef71765113ea0caef2bd7ce9594
Однако суперпроект *1214*, в котором я нахожусь, все еще вызывает другой коммит:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: sub (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
Команда git diff
, которая сравнивает индекс с рабочим деревом, говорит:
$ git diff
diff --git a/sub b/sub
index 8ffac73..ca09e95 160000
--- a/sub
+++ b/sub
@@ -1 +1 @@
-Subproject commit 8ffac73422c73898facacb7a0f92ed15a29cc7ad
+Subproject commit ca09e95a23e28ef71765113ea0caef2bd7ce9594
Теперь я могу запустить git add sub
и git commit
, чтобы создать новый коммит, который почтиточно так же, как мой старый коммит, за исключением того, что идентификатор хэша, который он сообщает Git для извлечения, если я должен был запустить git submodule update
- без --remote
- теперь ca09e95a23e28ef71765113ea0caef2bd7ce9594
:
$ git add sub
$ git commit -m 'update submodule'
[master fd09d9b] update submodule
1 file changed, 1 insertion(+), 1 deletion(-)
Если бы у меня были другие измененные или новые файлы, мне бы пришлось также git add
их скопировать в индекс, чтобы они вошли в новый коммит.
Обратите внимание, что еслиЯ осторожно избегаю git add sub
- а также такие вещи, как git add -a
или git add -u
, которые обновят sub
- затем любые новый коммит, который я сделаю, будет не иметь новый хэш-идентификатор для sub
, а скорее будет иметь старый хэш-идентификатор для sub
.Если кто-то проверяет этот конкретный коммит и затем запускает git submodule update
(без --remote
снова), его суперпроект Git скажет своему подмодулю Git проверить старый коммит, а не новый.
Также возможно, если вы случайно git add
ed подпрограмма, использовать git reset
, чтобы установить его обратно перед фиксацией:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: sub (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
$ git add sub # oops!
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: sub
$ git reset sub
Unstaged changes after reset:
M sub
Теперь, даже если сам подмодуль включен ca09e95a23e28ef71765113ea0caef2bd7ce9594
, индекс в суперпроекте все еще говорит 8ffac73422c73898facacb7a0f92ed15a29cc7ad
.
2 Эти имена, HEAD:sub
и :0:sub
, являются синтаксис gitrevisions для указания конкретных объектов.Команда git rev-parse
превращает их в хэш-идентификаторы для базового объекта Git.В данном случае это хеш-код дерева или индекса для подмодуля.
При получении запросов
Git itУ self нет запросов на выборку. 3 Вместо этого они являются функцией веб-сайтов, таких как GitHub.Все, что Git действительно имеет, это коммиты, хранящиеся в репозиториях.Чтобы создать запрос на загрузку на GitHub:
Вы должны git push
свои фактические коммиты в некоторый реальный репозиторий.Это может быть основной или вспомогательный файл, созданный вами с помощью кнопки GitHub «fork a repository».Git требует, чтобы ваши коммиты были как-то связаны с этим основным хранилищем.Главный, конечно, - это основной - он довольно хорошо подключен! - и кнопка "fork" на GitHub создает закулисную ссылку с вашего форка на основной, так что либо будет обслуживать.
Теперь, когда ваши коммиты находятся где-то на GitHub, в основном репозитории или связаны с ним, вы используете больше кликабельных кнопок веб-интерфейса GitHubвыбрать конкретную ветку в основного хранилища.Затем GitHub пытается за кадром сделать git merge
4 , используя специфичное для GitHub ссылочное имя.Если слияние проходит успешно, GitHub представляет запрос на извлечение всем, кто работает с основным репозиторием, позволяя им использовать щелкающие кнопки в веб-интерфейсе для выполнения слияния.
Таким образом, то, что вы получаете с помощью этого запроса на получение ответа, по сути, дает возможность кому-то другому повторить git merge
, который вы вызвали.Легко сказать, что сделает git merge
: вы можете сделать git merge
самостоятельно.Таким образом, запрос на извлечение изменит хеш подмодуля тогда и только тогда, когда git merge
также успешно изменит хеш подмодуля.Это ничего не могло сделать.Это может даже привести к конфликту слияния хеша подмодуля!
Итак: когда git merge
изменит хеш подмодуля?Это то же самое, что когда git merge
изменит любой другой файл.Что делает git merge
, так это находит фиксацию базы слияния, а затем запускает, по сути, две git diff
команды: одну, чтобы сравнить базу слияния с вершиной ветви, с которой вы объединяете и один для сравнения базы слияния с верхушкой ветви, которую вы объединяете из .Затем Git применяет оба набора изменений ко всем измененным файлам, начиная с файлов из базы слияния.
Допустим, вы сливаете develop
, где вы работали, в master
(черезgit checkout master && git merge develop
).Обратите внимание, что --ours
теперь является веткой master
и ее коммитом, в то время как --theirs
является вашим коммитом: вы поменялись ролями, чтобы быть тем, кто собирается нажать кнопку GitHub «merge» позже.Итак, три интересных коммита:
- вершина
master
: это левая сторона, или локальный, или --ours
коммит; - вершина
develop
: это правая сторона, или удаленная, или другая, или --theirs
commit;и - база слияния (независимо от того, какой печатается хеш-код
git merge-base --all master develop
, если предполагается, что он печатает только один хэш-идентификатор).
Если base-vs- master
нет изменить хеш подмодуля, но base-vs- develop
делает изменение хеша подмодуля, слияние будет успешным и изменение хеша подмодуля: объединение забирает их (your) change.
Если base-vs- master
действительно меняет хеш подмодуля, но base-vs- develop
не не меняет хеш подмодуляслияние будет успешным и сохранит хэш master
: слияние не принимает их (ваше) изменение, поскольку таких изменений нет.
Если base-vs- master
делает изменить хеш подмодуля и base-vs- develop
также меняет хеш подмодуля, им лучше оба поменять хеш на тот же хеш.Если это так, изменения совпадают, и Git принимает одно изменение.Если нет, изменения вступают в конфликт, и Git объявляет конфликт слияния и останавливается (или GitHub делает запрос на удаление, который не может быть объединен).
Итак, хитрость здесь, если вы хотите , а не предложить изменение хеш-идентификатора подмодуля, это убедиться, что ваш запрос на получение запроса фиксации - тот, который будет --theirs
фиксацией, когда он приходит время слияния - использует тот же хеш подмодуля, что и в базе слияния, независимо от того, какой коммит это может быть. Обратите внимание, что база слияния master
и develop
зависит от хэшей фиксации, хранящихся в master
и develop
. Если master
меняется с течением времени - как это часто бывает - возможно, что хэш базы слияния, который вы вычислили во вторник, неверен в среду. Следовательно, в некоторой степени - действительно, довольно большой степени - погоня за хешем коммитов базы слияния для подмодуля является бессмысленным поручением. Требуется только в случае конфликта слияния, и в этом случае проще просто получить непосредственно хеш-идентификатор коммита master
, так что два изменения - base-vs-master и base- против развития - это то же самое изменение.
В конце концов, это означает, что эти конфликты хеш-идентификаторов, если и когда они происходят, обычно являются лишь незначительным неудобством. Вы можете попытаться не допустить, чтобы ваши коммиты обновляли хеш-код подмодуля (когда-либо), избегая git add
использования подмодуля или git reset
-ing, если вы случайно добавили его. (Вы также должны избегать git commit -a
, который добавит его, а затем совершит коммит, не давая вам возможности сбросить его.)
3 У Git есть команда git request-pull
, которая создает сообщение электронной почты , предлагающее, чтобы кто-то использовал git pull
или git fetch
для получения коммитов из репозитория, которым вы управляете. Чтобы использовать эту команду, вы помещаете коммиты в свой репозиторий, делаете свой репозиторий доступным для других лиц, создаете сообщение электронной почты и отправляете его этим другим лицам. Затем они могут запустить git fetch
или git pull
вручную, используя URL вашего хранилища с их end.
(Интерфейс GitHub для большинства людей намного проще в использовании.)
4 Технически, GitHub должен сделать здесь что-то особенное, потому что все их репозитории являются --bare
репозиториями, без рабочего дерева. Команда git merge
не будет работать без рабочего дерева. Но в любом случае они выполняют слияние особым образом и не собираются разрешать конфликты, поэтому им важны только те, которые могут автоматически завершиться. Если вы делаете git merge
в своем собственном, не обнаженном хранилище, и оно автоматически завершается, GitHub также автоматически завершает работу и делает то же самое, что и вы.