Вы правы, что на вашей диаграмме (из git-scm.com / docs / git-merge ) показано слияние с общим коммитом предка.Стоит отметить, что это общий коммит; термин branch в Git довольно хитрый.(См. Что именно мы подразумеваем под "ветвью"? )
В любом случае, я думаю, это поможет, если вы забудете, что git pull
даже существует.Все, что git pull
делает, это запускает две разные команды Git для вас.Вам будет лучше, пока вы не разберетесь с Git, используя отдельные команды git fetch
и git merge
.(Обратите внимание, что git pull --rebase
переключает вторую команду на git rebase
, но мы не будем здесь вдаваться в подробности.) Есть несколько проблем с использованием git pull
для запуска двух других команд.Одна из них заключается в том, что git pull
использует странный синтаксис только для извлечения, отличный от всех других команд Git, включая git merge
, который запускается git pull
.То есть вместо git pull origin xyz
вы будете запускать git merge origin/xyz
.Чтобы увидеть, что это такое, вы должны запустить git log origin/xyz
или git show origin/xyz
и т. Д. Они всегда пишутся origin/xyz
с косой чертой, кроме случаев использования git pull
- поэтому не используйте git pull
.:-) Давайте разберем его на две отдельные команды.
Первая команда git pull
выполняется git fetch
, которую вы можете запустить в любое время: git fetch
вызовкакой-то другой Git спрашивает его, какие коммиты он имеет для вас, под какими именами (обычно это имена веток и тегов).Он собирает эти коммиты (и, конечно, их файлы) и для каждого из их имен ветвей создает или обновляет ваши имена для удаленного отслеживания.Так вот откуда взято origin/master
, например: git fetch
видит, что у них есть master
, что их хозяин - коммит badf00d
или что-то еще, и создает или обновляет ваш origin/master
, чтобы запомнить: *Мастер 1045 * был badf00d
последний раз, когда я проверял.
Вторая команда, которую git pull
запускает для вас, - это то место, где находятся все интересные действия.Эта вторая команда должна не выполняться в любое старое время, в любой старой ветке, потому что, какая бы вторая команда у вас ни выполнялась, Git, эта должна быть в правой ветви:вы хотите слиться или тот, который вы хотите перебазировать.Я нахожу, что использование отдельных команд помогает здесь, потому что более ясно, что git merge
повлияет на ветку current , даже если вы назовете что-то вроде origin/master
.
Теперь, когда мы знаем, --allow-unrelated-histories
- это действительно вариант git merge
, давайте углубимся в git merge
и посмотрим, что он делает.Сначала мы посмотрим, что он делает с общей отправной точкой, затем снова на то, что он делает без единицы.
Объединение - это объединение изменений, так как общееначальная точка
Рассмотрим приведенную выше диаграмму, которую я немного перерисую:
A--B--C <-- topic
/
D--E--F--G <-- master (HEAD)
Это указывает на то, что, кто бы ни работал над topic
, он начал с проверкиout commit E
.Возможно, в то время коммит E
был последним коммитом на master
:
D--E <-- master, topic
С тех пор кто-то добавил два коммита на master
, что составляет F
и G
, а кто-то - возможно, кто-то еще - добавил три коммита на topic
, которые теперь являются цепочкой A-B-C
(родительский коммит A
равен E
).
Каждый коммит представляет собой полный снимок всех исходных файлов.Так что в commit E
есть все файлы - ну, все файлы, которые были у вас, когда вы или кто-то другой сделал коммит E
- сохранялись в этой форме навсегда.Любые изменения, внесенные вами или кем бы то ни было в любые файлы из этого сохраненного состояния и сохраненные, скажем, при фиксации A
, приводят к тому, что эти файлы переходят в новое состояние в A
.Любые неизмененные файлы в A
просто точно соответствуют файлам в E
.
Длядля простоты, мы предположим, что здесь действуют два человека, «вы» и «они», и вы внесли изменения в master
, что в итоге привело к фиксации G
.Затем они сделали A
до C
.Итак, вы и они оба начали с того, что всегда сохраняется в коммите E
.Вы получили то, что вы сохранили навсегда в G
.Таким образом, Git может узнать , что вы изменили с помощью простого git diff
, чтобы сравнить коммит E
с коммитом G
.Аналогично, они оказались на C
, поэтому Git может узнать, что они изменили на аналогичное простое git diff
, сравнивая E
против C
:
git diff --find-renames <em>hash-of-E hash-of-G</em>
: что вы изменили git diff --find-renames <em>hash-of-E hash-of-C</em>
: что они изменили
Затем Git извлекает файлы из commit E
, то есть с чего вы оба начали, объединяет ваши изменения в этих файлах и создает новый коммит с объединенными изменениями в них.Это определяет, какие файлы / содержимое входят в коммит H
:
A--B--C <-- topic
/ \
D--E--F--G---H <-- master (HEAD)
Первый родитель нового коммита H
- G
, который был кончиком master
и является вашей веткойпроверено.Его второй родитель - C
, тот, которого вы сказали git merge
для слияния.
Обратите внимание, что когда Git делает все это объединение изменений, он легко справляется со всеми файлами, которые точноодинаковые в обоих подсказках ветвления, потому что независимо от того, что было в базе объединения, оба подсказки совпадают, поэтому оба файла одинаковы, и любой из них работает нормально.Это также легко сделать, если вы изменили файл X, а они этого не сделали, и где они изменили файл Y, а вы нет, потому что, опять же, он может просто взять вашу или их версию этих файлов.Только там, где вы по-разному касались одного и того же файла, Git должен усердно работать.
Несвязанные истории
Несовместимые истории возникают, когда нет общая связь между двумя наборами коммитов:
A--B--C <-- master (HEAD)
J--K--L <-- theirs
Ваши коммиты начинаются с C
и работают в обратном направлении, заканчиваясь A
.Коммиты не приходят раньше, чем A
: A
не имеет родителей.
Их коммиты начинаются, в данном случае, с L
(я пропустил много писем, чтобы покинуть комнату, чтобы вставить наше слияние).Родитель L
- K
, а родитель K
- J
, но у J
также нет родителей.Так что общей отправной точки нет вообще.
Если вы скажете Git объединить их, Git просто притворится, что есть.Предполагаемая начальная точка имеет файлы no .Git работает:
git diff <em>empty-tree hash-of-C</em>
: что вы изменили git diff <em>empty-tree hash-of-L</em>
: что они изменили
Конечно, что вы изменили, из этогоdiff, это то, что вы добавили каждый файл (это в вашем коммите C
).Что они изменили, так это то, что они добавили каждый файл (это в их коммите L
).
Если файлы имеют разные имена, это разные файлы и проблем нет: Git принимаеттвой или их.Если у них одинаковые имена, но одинаковое содержимое, здесь тоже нет конфликта: Git может просто взять ваши (или их).Проблемы возникают для всех файлов, у которых у вас и у них одинаковое имя, но разное содержимое.Что касается Git, вы взбили свои с нуля, как и они, так что все конфликтует.Вы должны выбрать выигрышное содержимое или создать новый файл из входных данных «все конфликты».
Как только вы разрешите любой из этих конфликтов и запустите git merge --continue
, чтобы завершить Git, Git выполняет коммит слияния как обычно:
A--B--C--D <-- master (HEAD)
/
J--K---L <-- theirs
У нового коммита есть два родителя, C
и L
, и он навсегда сохраняет созданный вами снимок, исправляя конфликты, о которых сообщал Git, и другие файлы, которые были точното же самое в C
и L
или только в C
, или только в L
.
(«Навсегда» слишком сильно: сохраненные файлыдлится только до тех пор, пока сам коммит. Однако по умолчанию каждый коммит живет вечно. Если вы делаете коммит исчезающим, файлы тоже.)