Git не имеет запросов на получение. 1 Git Hub имеет запросы на получение, как и другие провайдеры веб-хостинга, которые позволяют вам использовать Git в качестве службы.Существует несколько базовых механизмов Git, для которых эти сервисы создают необычный, более удобный для пользователя интерфейс.
В частности, вы спросили о git merge
.Можно использовать эту команду - и / или модные веб-интерфейсы, которые предоставляют GitHub и другие, - не понимая, что происходит.Я не думаю, что это хорошая идея, сам.Однако, чтобы понять, что на самом деле происходит , есть несколько основных понятий, которые нужно сначала «получить».
Одно из них связано со значением термина сама ветвь : см. Что именно мы подразумеваем под «ветвью»? Когда кто-то говорит «ветвь», иногда они имеют в виду ветвь имя - или одну из Git'sразличные другие имена, которые не совсем совпадают с именами ветвей - и иногда они означают неопределенно определенную серию коммитов .
Опять же, для использования интерфейса запросов на выборку в GitHub ничего из этого не требуется,Чтобы использовать это, вы просто нажимаете несколько веб-кнопок.Но хорошая идея - я думаю, по крайней мере - знать, что произойдет с этими кнопками, и для этого вам do необходимо понять, как работает Git.Итак, давайте углубимся в детали.
1 Под этим я подразумеваю, что команды git pull-request
нет.Команда - это a git request-pull
, и она генерирует сообщение электронной почты.Это степень, в которой Git поддерживает запросы на извлечение: у него есть команда для генерации сообщения электронной почты с просьбой сделать что-то еще.
Commits
Коммит Git - это снимок экранавсе ваши файлы в том состоянии, в котором они находились, когда вы делали снимок, плюс немного метаданных , описывающих снимок.(Технически, фиксация представляет моментальный снимок косвенно, поскольку снимок хранится отдельно как объект tree , но в большинстве случаев вам не нужно знать об этом: существует односторонняя ссылка от фиксации к снимку, так что с учетом коммита Git всегда может найти моментальный снимок. Связь означает, что несколько разных коммитов могут представлять один и тот же снимок, не занимая пробела дважды, что полезно для несколькихцели.)
Каждый коммит идентифицируется по хеш-идентификатору, например b5101f929789889c2e536d915698f58d5c5c6b7a
.Эти вещи большие и уродливые и невозможные для людей, но именно так Git находит коммитов, поэтому они имеют решающее значение для работы Git.Хеш-идентификатор для любого конкретного коммита всегда уникален: этот хеш-код равен , - коммит и ни один другой, никогда;каждый другой коммит имеет другой идентификатор хеша.Более того, все Gits во всей вселенной согласны с этими вычислениями хеш-идентификатора. Учитывая два разных Git-репозитория, если у них обоих есть некоторый идентификатор фиксации H - то есть они оба имеют фиксациюобъект, хэш которого равен H - содержимое этого объекта обязательно должно быть таким же. 2
Метаданные в коммите содержат ваше имя(или имя человека, который совершил коммит) и адрес электронной почты, а также отметку времени, когда коммит был сделан.Он включает в себя ваше сообщение журнала, сообщающее всем , почему вы сделали этот коммит.Но он также включает в себя хэш-идентификатор коммита, который приходит сразу за до этого.Мы называем это родительским коммитом .Результатом является задняя цепь.Если у нас есть крошечный репозиторий с тремя коммитами, мы можем нарисовать его так:
A <-B <-C
Здесь, C
это последний коммит.У него есть какой-то уникальный, большой уродливый идентификатор хеша.Он также хранит уникальный хэш-идентификатор commit B
, поэтому, как только мы найдем C
, мы можем использовать его для поиска B
.Между тем B
имеет хэш-идентификатор A
, поэтому мы можем использовать B
, чтобы найти A
.Поскольку A
является самым первым коммитом, у него вообще нет родителя - технически это root коммит - и это позволяет нам перестать возвращаться назад.
Еще одна вещь, которую нужно знать о коммитах- и все объекты Git, которые имеют хэш-идентификаторы, - это то, что вы не можете изменить ни одного из них, никогда.Причина в том, что идентификатор хеша является криптографической контрольной суммой содержимого объекта.Если бы вы взяли объект коммита и изменили хотя бы один бит - например, исправили написание слова в сообщении журнала - вы бы получили новый и другой коммит с другим идентификатором хэша,Итак, как только сделана фиксация, она навсегда: хэш-идентификатор уже взят. 3
Для нас это означает, что нам не нужноНарисуйте стрелки, выходящие из коммита как стрелки.Как только коммит существует, он постоянен, и его связи с родителями тоже постоянны.Нам просто нужно помнить, что они только идут в одну сторону: назад.Новые ссылки могут появляться на этого коммита, но они не могут появляться при переходе с этого коммита в любое новое место.
2 Обратите внимание, что требование, чтобы идентичные хэш-идентификаторы представляли один и тот же контент базового объекта, поддерживается только для двух Git, которые встречаются и обмениваются объектами.Два репозитория, которые никогда не подключаются , могут , имеют такие коммиты двойника, если они никогда не пытаются общаться друг с другом.
3 Вы можете удалить коммит целиком, хотя и немного болезненно, просто ничего не ссылаясь на него.В конце концов, лежащий в основе объект Git удаляется, эффективно освобождая идентификатор хеша.Поскольку в текущей системе хеширования имеется 160 битов, в любом репозитории Git есть только 2 160 возможных объектов.К счастью, этого достаточно.Тем не менее, принцип вихря в сочетании с парадоксом дня рождения *1127* создает некоторые интересные теоретические проблемы, и Как недавно обнаруженное столкновение SHA-1 влияет на Git? обсуждаетсяоб этом.
Имена ветвей
Учитывая приведенный выше репозиторий, мы можем найти all коммитов, если мы знаем хеш-идентификатор commit C
.Где мы будем хранить это?Как насчет: в имени ветви? Давайте выберем имя наподобие master
и используем его, чтобы записать хеш-код C
:
A--B--C <-- master
Теперь давайте сделаем new commit, проверяя commit C
и выполняя некоторую работу, обычным способом:
git checkout master
... do some work ...
git add ... various files ...
git commit
Новый commit упакует новый снимок, добавит любое предоставленное нами сообщение журналадобавьте наше имя и адрес электронной почты, а также отметку времени и, самое главное, установите родительский элемент нашего нового коммита в качестве коммита C
:
A--B--C--D
в качестве последнего шагапри фиксации git commit
примет новый хэш-идентификатор нового коммита - какой бы ни была действительная контрольная сумма, теперь, когда все части скреплены навсегда, - и * запишите эту контрольную сумму в имени master
:
A--B--C--D <-- master
Так вот, что такое имя ветки: это место для хранения хеш-идентификатора последнего коммита.Просто людям не нужно запоминать хэш-идентификаторы, потому что у нас есть Git, чтобы помнить их за нас.Мы помним только, что master
содержит хэш-идентификатор последнего коммита, а Git сделает все остальное.
Вот где ваша HEAD входит
Конечно, вы можете создать несколько имен филиалов.Каждый из них просто указывает на один конкретный коммит.Давайте создадим новую ветку dev
прямо сейчас:
A--B--C--D <-- master, dev
Обратите внимание, что master
и dev
оба указывают на фиксацию D
, а все четыре фиксации находятся в обеих ветвях .Но Git нужен способ узнать , какое имя изменить , когда мы сделаем новый коммит.Вот где появляется специальное имя HEAD
. Мы должны Git присоединить это имя к одному (и только одному) имени ветви:
A--B--C--D <-- master (HEAD), dev
или:
A--B--C--D <-- master, dev (HEAD)
Мы делаемпри этом используется git checkout
, который не только проверяет коммит, но и присоединяет HEAD
.Если HEAD
присоединен к master
и мы делаем новый коммит E
, он выглядит следующим образом:
E <-- master (HEAD)
/
A--B--C--D <-- dev
Если мы теперь переключим HEAD
на dev
(выполнив git checkout dev
) и сделаем новый коммит F
, мы получим:
E <-- master
/
A--B--C--D
\
F <-- dev (HEAD)
Вот где происходят слияния
Допустим, у нас есть репозиторий с кучей коммитов, куда смотрят последние нескольковот так:
I--J <-- br1 (HEAD)
/
...--H
\
K--L <-- br2
Здесь у нас есть несколько коммитов, заканчивающихся тем, чей хэш равен H
, а H
содержит некоторый снимок.Затем кто-то - может быть, мы - сделали еще два коммита I
и J
на ветке br1
, на которых мы сейчас находимся.Кто-то - может быть, мы, может, кто-то другой - начали с H
и сделали еще два коммита K
и L
.Это верно , даже если кто-то другой сделал K
и L
в другом хранилище. У нас обоих H
, и поскольку все Gits везде согласны с вычислением хеш-идентификатора, мы оба запускается с того же коммита .
Что команда git merge
сделает, это выяснит что мы изменили в нашей ветке br1
и что они изменилив их ветке br2
.Затем объединит эти изменения.Но мы уже отмечали, что слово branch имеет тенденцию быть расплывчатым и плохо определенным.Что Git будет действительно делать здесь, так это найти common commit , тот, с которого мы оба начали.Мы уже видели, что это commit H
.
Commit H
, следовательно, база слияния операции слияния.Два других интересных коммита - просто тот, который назван нашей текущей веткой - commit J
в конце br1
- и тот, который назван другой ветвью, commit L
в конце br2
.Все три коммита снимки , поэтому Git нужно сравнить их:
git diff --find-renames <em>hash-of-H hash-of-J</em>
находит то, что мы сделали в br1 git diff --find-renames <em>hash-of-H hash-of-L</em>
находит то, что они сделали в br2
Git теперь может объединить эти два набора изменений .Если мы изменили какой-то файл, а они этого не сделали, Git должен взять наш новый файл.Если они изменили какой-то файл, а мы нет, Git должен взять их новый файл.Если мы оба изменили один и тот же файл, Git должен начать с копии файла из самой фиксации базы слияния H
, объединить два разных изменения и применить объединенные измененияв этот файл.
Вот что делает git merge
, в данном случае: он объединяет наши изменения и их изменения, что создает новый объединенный снимок.Этот процесс объединения изменений - это то, что я люблю называть слиянием как глагол или слиянием .Важно помнить, что это может быть сделано другими командами, потому что другие команды Git делают это!Это для слияния или слияния как глагол использует Git's merge engine для объединения работы.
В этом случае, однако, git merge
сейчаспродолжает делать коммит слияния .Это почти обычный коммит: у него есть снимок, сообщение в журнале и т. Д., Как и любой другой коммит.Что делает его особенным - коммит слияния - то, что он имеет два родительских коммита вместо обычного.Родитель first такой же, как обычно: это коммит, который мы извлекли при запуске git merge
. второй родитель - это просто другой коммит - тот, который мы выбрали, используя имя br2
, или, в данном случае, коммит L
.
Так что теперь git merge
делает слияние (слияние как существительное) или слияние (слияние как прилагательное), которое выглядит следующим образом:
I--J
/ \
...--H M
\ /
K--L <-- br2
Что происходитна имя нашей ветки?То же самое, что и всегда, конечно.Git записывает новый хэш-идентификатор для этого нового коммита слияния M
в текущее имя ветви:
I--J
/ \
...--H M <-- br1 (HEAD)
\ /
K--L <-- br2
Вот как мы будем объединять чьи-то коммиты - кто-то, кто бы ни сделал K
и L
вbr2
, в данном случае.
(Обратите внимание, что, как правило, мы получаем тот же снимок , если мы git checkout br2; git merge br1
. База слияния по-прежнему H
и две подсказкиравны L
и J
, и объединение работы дает тот же результат. Что изменится, так это то, что первым родителем этого другого слияния будет L
, а не J
, поэтому родители меняются местами, а окончательное имя-update обновит имя br2
, а не br1
. Если мы начнем добавлять дополнительные параметры слияния, хотя, например, -X ours
или -X theirs
, другие вещи могут отличаться.)
Не всеgit merge
команды приводят к слияниям
Здесь стоит отметить одну или две дополнительные складки.Предположим, у нас есть этот график:
...--A--B--C--D--E--H--I <-- branch1 (HEAD)
\ /
F----G <-- branch2
и мы запускаем git merge branch2
.Мы уже объединили branch2 ранее при коммите H
, у которого есть родители E
и G
.База слияния определяется (в общих чертах - технически это самый низкий общий предок в группе обеспечения доступности баз данных) как ближайший коммит, который находится на обеих ветвях, и это коммит G
, поскольку с I
мы можем идти назад до H
и затем G
и, конечно же, с G
мы просто остаемся прямо там на G
.
В этом случае git merge branch2
скажет уже в курсе и ничего не сделает.Это верно: их коммит G
, а наш I
, у которого уже есть G
в качестве предка (в данном случае дедушки и бабушки), поэтому нет новой работы для объединения.
Мы можемТакже есть такая ситуация:
...--A--B--C--D--E--H--I <-- branch1
\ /
F----G <-- branch2 (HEAD)
, где мы запускаем git merge branch1
.На этот раз наш коммит равен G
, а их равен I
.База слияния по-прежнему фиксирует G
, как и раньше.Что Git делает по умолчанию в этой ситуации, так это говорит самому себе: Результат сравнения G
против G
по определению пуст.Результат объединения чего-либо с чем-либо по определению является чем-то.Так что все, что мне действительно нужно, это git checkout hash-of-I
.Так что я сделаю это, но в то же время, сделайте имя branch2
точкой для фиксации I
. Результат:
...--A--B--C--D--E--H--I <-- branch1, branch2 (HEAD)
\ /
F----G
Git называет это ускоренная перемотка вперед .Git иногда называет это ускоренным слиянием , что не является хорошей терминологией, поскольку в нем нет фактического слияния.
Вы можете заставить Git выполнить реальное слияние - diff G
против себя и ничего не объединять с чем-то, а затем сделать реальный коммит слияния, давая:
...--A--B--C--D--E--H--I <-- branch1
\ / \
F----G------J <-- branch2 (HEAD)
Чтобы вызвать реальное слияние здесь, используйте git merge --no-ff branch1
.
(Иногда вы хотитеили требуется реальное слияние, а иногда быстрая перемотка вперед - вместо этого все в порядке. Для чего бы это ни стоило, щелкающие кнопки на интерфейсе веб-хостинга GitHub не не разрешают или не выполняют слияние ускоренной перемотки, даже если вы хотитеих к. В сущности, они всегда используют git merge --no-ff
.)
Как все это относится к запросам на получение
Запрос на получение или даже более примитивный вариант Git git request-pull
, полезны толькоесли в процессе задействовано более одного Git-репозитория .
В этом случае в репозитории # 1 может иметь место серия коммитов:
I--J <-- master
/
...--H
Между тем, в репозитории № 2 мы имеем:
...--H
\
K--L <-- master
Поскольку это два разных репозитория, у них есть своя собственная ветвь names .У каждого есть его основной хэш-идентификатор ID I
.Другой имеет свой основной хэш-идентификатор ID L
.Коммит H
находится в обоих репозиториях, тогда как коммиты I-J
находятся только в # 1, а K-L
только в # 2.
Если бы мы каким-то образом объедините два хранилища, изменив имена, чтобы они не сталкивались, мы вернемся к нашей обычной ситуации слияния:
I--J <-- master of Repository #1
/
...--H
\
K--L <-- master of Repository #2
Th это именно то, что делает GitHub с его клики веб-интерфейсом.Кем бы вы ни были - № 1 или № 2;давайте выберем №2 для конкретности - вы скажете GitHub: Я бы хотел, чтобы они объединили моего мастера, т. е. передали L. GitHub, а затем скопировали ваши коммиты - побитно, так чточто их хэш-идентификаторы остаются неизменными - в их хранилище, помещая хеш-идентификатор commit L
под специальным именем, которое , а не master
, и не называет другое имя ветви. 4 Затем они, GitHub, запускают git merge
, используя такое же специальное имя, которое вообще не является именем ветви.Если все это работает, , тогда они сообщают, кто бы ни контролировал хранилище # 1, что у вас есть запрос на извлечение.
Тот, кто контролирует хранилище # 1, теперь может нажать кнопку "объединить запрос на извлечение".Это берет слияние, которое GitHub уже выполнил 5 и перемещает их master
, или любое другое имя ветви, в их хранилище GitHub, соответственно:
I--J
/ \
...--H M <-- master
\ /
K--L
CommitsK
и L
теперь появляются в их хранилище, доступ к которому можно получить, перейдя по обратной ссылке второго родителя из master
.
Что это значит для вас, как человека, который хочет сделать запрос на извлечение заключается в том, что вы должны организовать для своего хранилища на GitHub коммит или цепочку коммитов, которые GitHub сможет протестировать для вас.Затем GitHub представит запрос владельцу этого репозитория, и этот владелец сможет сделать один клик, чтобы завершить слияние, обновив имя своей ветви, чтобы использовать тестовое слияние, выполненное GitHub.
Коммитыи результат теста слияния определяются тем, какие коммиты вы поместили в свой репозиторий GitHub.Если у вас есть собственный отдельный репозиторий на вашем компьютере локально, вы можете поместить коммиты в it и использовать git push
для отправки этих коммитов в в ваш репозиторий GitHub.
Это, очевидно, все немного запутанно, но если вы синхронизируете репозиторий локального компьютера и свой собственный репозиторий GitHub, чтобы они всегда "выглядели одинаково", вы можете проигнорировать дополнительный слой здесь.Проблема с игнорированием этого слоя в том, что он все еще там!Если вы позволите своему хранилищу и вашему хранилищу GitHub выйти из синхронизации, это снова будет видно.
4 Когда вы делаете запрос на извлечение, GitHub выделяет для него уникальный номер(уникальный для репозитория назначения).Скажите, что это запрос на извлечение № 123.Имя, которое GitHub использует для ваших коммитов после их копирования в репозиторий GitHub, - refs/pull/123/head
.Имя, которое GitHub использует для объединения тестов, это refs/pull/123/merge
.Если тестовое слияние завершается неудачно с конфликтом, GitHub все равно не создает его и не создает второе имя.
5 Если кто-либо, контролирующий хранилище PR-цели, нажимает new фиксирует их ветку , тестовое слияние, выполненное GitHub, становится недействительным («устарело»).GitHub сделает новое тестовое слияние, если и когда это будет уместно.Я не уверен, что они удаляют имя refs/pull/123/merge
между ними, поскольку я никогда не проверял это.