Git субмодулей совершает без моего ведома ..? - PullRequest
0 голосов
/ 17 ноября 2018

Немного личного опыта: раньше я использовал подмодули git, но, если честно, я не фанат и полностью признаю, что, возможно, это мое незнание нюансов о том, как они работают.Когда я использовал их ранее для разделяемых библиотек, я извлекал обновления с git pull в подмодуле, который затем показывался как modified … (new commits) в родительском модуле.Я получил это - я изменил указатель фиксации в метаданных .git или что-то в этом духе, и мне нужно commit и push, которые изменятся в родительском репо, чтобы он ассоциировался с правильной фиксацией подмодуля.

Моя проблема: я сейчас на новой работе, и у проекта, над которым я работаю, есть субмодуль.Это не разделяемая библиотека - процесс сборки опирается на некоторые учетные данные, которые по соображениям безопасности обновляются ежедневно и распространяются через субмодуль.Таким образом, происходит ежедневный процесс получения обновлений, который происходит с помощью git submodule update --remote.

Две странности: (1) подмодуль постоянно находится в состоянии отсоединенной головы;и (2) status родительского элемента остается чистым, не показывая изменений в подмодуле.

Так почему же это проблема?Проблема в том, что мои запросы на включение отображаются с субмодулем в списке «измененные файлы».Я не думаю, что это на самом деле приводит к проблеме, но один рецензент делает конкретное исключение из этих изменений, так как их не должно быть.Поскольку подмодуль никогда не отображается как измененный в выводе git status, я не знаю, как я фиксирую какие-либо изменения и как это остановить.

(Это частное репо в GitHub -У меня есть форк, и я работаю в ветке моего форка. Субмодуль остается направленным на не разветвленного мастера для его репо.)

(Очевидно, это также очевидно в PR других разработчиков, ноне в каждом пиаре.)

Я новичок в этой компании, и мог бы обойтись без того, чтобы быть нарисованным как кто-то, кто не может правильно работать с версионным контролем!Но мне нужен кто-то с большей мудростью, чем я, чтобы сказать мне, что происходит.

(Лично я бы решил не использовать подмодуль , не то чтобы это не было в моемконтроль.)

1 Ответ

0 голосов
/ 18 ноября 2018

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:

  1. Вы должны git push свои фактические коммиты в некоторый реальный репозиторий.Это может быть основной или вспомогательный файл, созданный вами с помощью кнопки GitHub «fork a repository».Git требует, чтобы ваши коммиты были как-то связаны с этим основным хранилищем.Главный, конечно, - это основной - он довольно хорошо подключен! - и кнопка "fork" на GitHub создает закулисную ссылку с вашего форка на основной, так что либо будет обслуживать.

  2. Теперь, когда ваши коммиты находятся где-то на 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 также автоматически завершает работу и делает то же самое, что и вы.

...