Вы смешиваете несколько понятий, которые должны быть отдельнымиВ частности, запрос pull представляет собой функцию GitHub (или другого поставщика веб-услуг).Это не то, что Git делает. 1 Между тем git merge
- это команда командной строки Git, которая работает в вашем собственном хранилище, расположенном на вашем собственном компьютере (ноутбуке).или что угодно).Работая с GitHub pull-запросами, вы - или, по крайней мере, любой, кто соглашается на запрос - можете манипулировать некоторыми кнопками браузера, чтобы выполнить «слияние», «перебазирование и слияние» или «сквош и слияние», иони вносят изменения в репозиторий, размещенный на GitHub.Они связаны между собой, но различаются по нескольким важным аспектам, самый большой из которых - , у которого действительно есть хранилище .Если у вас есть более одного Git-репозитория, важно помнить, какой из них имеет коммит, на который указывают имена веток, потому что ваши имена ветвей ваши , а ихих: им не нужно иметь ничего общего друг с другом, даже если их обоих называют master
или topic
или feature/short
или что-то еще.
1 Git имеет команду git request-pull
, которая генерирует (но не отправляет) сообщения электронной почты.Это явно не то же самое, что запрос на получение в стиле GitHub.
Слово merge в Git является существительным и глаголом
. gitglossary определяет слияние таким образом:
как глагол: для переноса содержимого другой ветви (возможно, из внешнего хранилище ) в текущую ветку.В случае, когда объединенная ветвь находится в другом хранилище, это делается сначала путем выборки удаленной ветки и затем слиянием результата с текущей веткой.Эта комбинация операций извлечения и слияния называется pull .Объединение выполняется автоматическим процессом, который идентифицирует изменения, внесенные после того, как ветви разошлись, а затем применяет все эти изменения вместе.В случаях, когда изменения вступают в конфликт, может потребоваться ручное вмешательство для завершения слияния.
Как существительное: если это не fast-forward , успешное слияние приводит к созданию нового коммит , представляющий результат слияния и имеющий в качестве родителей кончики слитых ветвей .Этот коммит называется «коммитом слияния», или иногда просто «слиянием».
В эти два абзаца много всего упаковано, и я не фанат вклинивания определенияиз «тянуть» в глагола слияния - тем более, что git pull
можно сказать запустить git rebase
вместо git merge
в качестве его второй команды Git - но различие глагол / существительное здесь.Когда вы выполняете действие - глагол как часть слияния - вы часто получаете существительное или прилагательное, то есть коммит слияния , как результат.
Имея это в виду, давайте посмотрим на анатомию фактических слияний
Игнорирование git pull
(которое просто запускает git merge
для вас, если вы не скажете иначе), как выДля вызова основной операции слияния как глагол необходимо выполнить:
git merge <thing-to-specify-a-commit>
из командной строки.Аргумент спецификатора коммита может быть именем ветви, именем тега, необработанным идентификатором хэша коммита или чем-то, что позволяет Git найти один конкретный коммит.
В Git коммиты существуют в виде graph , в частности, направленный ациклический граф или DAG.Каждый коммит идентифицируется собственным уникальным хеш-идентификатором - хеш-код, по сути, является истинным именем коммита, и это конкретное имя используется в каждом Git-репозитории везде, что означает , что один конкретный коммит, если этот коммит вообще находится в этом Git-репозитории.Между тем, каждый коммит содержит, как часть своих метаданных, фактический хеш-код (ы) своего родителя (ей).
MathematВ действительности, определение графа: G = (V, E) , где V - набор вершин (или узлов), а E - набор ребра, соединяющие эти узлы. В Git узлы - это коммиты, идентифицируемые по их хэш-идентификаторам, а ребра - односторонние ссылки или arcs , указывающие от дочернего коммита на его родителя (-ей). Эти однонаправленные ссылки позволяют Git перемещаться, начиная с коммитов самого ребенка. Это дает нам множество способов рисовать график. Для сообщений StackOverflow мне нравится записывать их следующим образом, с новыми коммитами вправо:
... <-F <-G <-H
Здесь каждая буква заменяет фактический хеш-идентификатор, а стрелки, идущие от буквы, представляют собой ребра / дуги: если H
является коммитом, он содержит хеш-идентификатор своего родителя G
, поэтому H
указывает на G
. G
содержит идентификатор хеша F
, поэтому G
указывает на F
и так далее. Все эти стрелки идут назад - у типичного DAG есть стрелка, идущая от родителей, до детей, но Git предпочитает работать в обратном направлении (по разным причинам). 2 Для нашего же удобства мы можем просто начать рисовать их вообще без направления стрелки и просто помнить, что Git делает все задом наперед:
...--F--G--H
В прямом направлении - тот, который Git на самом деле не делает - commit F
имеет одного дочернего элемента G
, а G
имеет одного дочернего элемента H
. Отсюда, давайте H
приобретем двух детей, каждый из которых имеет одного из своих детей:
I--J
/
...--F--G--H
\
K--L
Для Git на найдите этих коммитов, ему нужны некоторые имена веток. Ветвь names содержит фактический хэш-идентификатор last commit в ветви - это означает, что каждый раз, когда мы добавляем new commit к ветви, значение связано с изменениями имени - но давайте просто нарисуем это сейчас:
I--J <-- br1
/
...--F--G--H
\
K--L <-- br2
Чтобы выполнить слияние - чтобы выполнить слияние с глаголом, - мы выберем одну ветвь для проверки, такую как br1
, а затем запустим git merge
на другой:
git checkout br1
git merge br2
Это запустит процесс слияния как глагол. Если все пойдет хорошо, он сделает коммит слияния , который будет просто коммитом как минимум с двумя родителями: 3
I--J
/ \
...--F--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
Этот новый коммит слияния M
добавляется как последний коммит в текущей ветви br1
, так что * имя br1
теперь указывает на новый коммит слияния M
.
Git на самом деле вычисляет содержимое - объединенные файлы - для фиксации слияния M
, используя три входных данных: два коммита tip - здесь, J
и L
- и базовый коммит слияния, который определяется как лучший общий предок двух других коммитов. В этом конкретном случае легко увидеть лучшего общего предка: это commit H
. 4 Вы можете представить себе, как Git обрабатывает это как:
- запустите
git diff --find-renames <em>hash-of-H</em> <em>hash-of-J</em>
, чтобы увидеть, что мы изменили;
- запустите
git diff --find-renames <em>hash-of-H</em> <em>hash-of-L</em>
чтобы увидеть, что они изменили;
- объединяет эти два вывода diff, применяя объединенные изменения к файлам из commit
H
.
Игнорирование всей механики разрешения конфликтов и различных особых случаев не слияния, которые может сделать git merge
, это действительно ключ к любому слиянию. Git находит общий исходный коммит, создает два набора различий, объединяет их и применяет объединенные разногласия к общему снимку начальной точки. Это формирует моментальный снимок для результата слияния.
2 Частичноicular, все в основной базе данных объектов Git - коммиты и файлы, а также различные объекты склеивания - навсегда заморожены. Git нуждается в этом, чтобы заставить работать его хэш-идентификаторы. Коммит знает своего родителя или родителей на момент создания: их хэш-идентификаторы уже существуют. Коммит еще не знает своих детей. Коммит только что родился! Его память сейчас заморожена. В конце концов, позже , он приобретает своего первого ребенка. Родитель не может узнать хеш-идентификатор своего дочернего элемента, поскольку фиксация уже заблокирована на все времена. То же самое верно, если и когда родитель получает второго ребенка, третьего ребенка и так далее. Дети знают своих родителей, но родители не знают своих детей.
3 Git вызывает коммит слияния с тремя или более родителями слияние осьминога . Вы ничего не можете сделать с слиянием осьминога, которое вы не можете сделать с помощью серии обычных слияний - на самом деле все наоборот: вы можете делать обычные слияния, которые Git откажется делать как одно слияние осьминога. Тот факт, что слияние осьминога на слабее , чем обычные слияния, несколько полезно, когда вы изучаете новый для вас репозиторий, поскольку вы можете быть достаточно уверены, что это слияние не вносит секретных изменений и не имеют разрешения конфликтов. Но другие системы контроля версий, которые допускают только слияния пары коммитов, так же мощны , что и Git.
4 Технически, база слияния является самым низким общим предком двух коммитов, используя обобщенную форму алгоритма LCA. LCA хорошо определены на деревьях - фрагмент графика, который я показываю выше, на самом деле является деревом, а не группой DAG, но в группах DAG может быть несколько LCA. В этом конкретном ответе я не собираюсь вдаваться в то, как Git обрабатывает случай множественных LCA.
Слияние сквоша теперь тривиально
Как только вы поймете все вышесказанное, git merge --squash
станет смехотворно простым. Git делает все, что он сделал бы для реального слияния , а затем, в конце, делает коммит с одним родителем вместо two . 5
То есть мы начинаем с:
I--J <-- br1
/
...--F--G--H
\
K--L <-- br2
и запустить git checkout br1; git merge --squash br2
. Git находит базу слияния H
, выполняет два сравнения, объединяет различия и применяет их к содержимому H
. Тогда Git делает - ну, делает вы делаете; см. сноску 5 - новый коммит слияния, но вместо M
с родителями J
и L
, Git делает коммит, который мы могли бы назвать S
(для сквоша) с одним родителем , J
:
I--J--S <-- br1
/
...--F--G--H
\
K--L <-- br2
Если бы Git произвел слияние M
- и мы на самом деле можем сделать M
, хешей не будут совпадать, но снимки из M
и S
будет. Вы можете подтвердить это, набрав M
:
git checkout -b experiment <hash-of-J>
git merge br2
, что приводит к:
I--J--S <-- br1
/ \
...--F--G--H M <-- experiment (HEAD)
\ /
K--L <-- br2
Содержимое из M
и S
будет соответствовать:
git diff br1 experiment
вообще ничего не покажет.
5 Когда вы запускаете git merge --squash
, Git заставляет вас запускать git commit
самостоятельно. Флаг --squash
включает флаг --no-commit
. Это остаток от древней версии Git, где --squash
просто сделал git merge
выход до работает git commit
- истинное слияние фактически вызывает git commit
в конце, если вы не используете --no-commit
или слияние имеет конфликты.