Здесь есть чему поучиться.Некоторые - ну, по крайней мере, один - называли кривую обучения Git "стеной обучения".По моему мнению, добавление веб-сервисов, таких как GitHub поверх Git, усложняет задачу: больше невозможно определить, есть ли что-то в Git или GitHub .Запросы Pull фактически попадают в категорию, предоставленную GitHub.Но они основаны на Git merge глагол (в сочетании с тем фактом, что GitHub хранит как ваш репозиторий, так и чужой репозиторий).
Давайтеотложите все это на некоторое время и начните с самого Git.Ключ к пониманию того, произойдет ли конфликт в каком-либо файле и когда это произойдет, скрыт за некоторой теорией графов .
Что входит в коммит
Во-первых,Отметим, что фиксирует файлы.Точнее, каждый коммит содержит моментальный снимок всех ваших файлов на момент совершения этого коммита.Но каждый коммит также имеет дополнительную информацию о коммите, например, кто его сделал, когда и почему (сообщение журнала).
В Git каждый коммит уникально идентифицируется своим хешемЯ БЫ.Это большая, некрасивая, в основном нечитаемая, совершенно бесполезная для человека строка символов, например 5d826e972970a784bd7a7bdf587512510097b8c7
(фактический коммит в Git-репозитории для Git).Это то, как Git находит коммиты, хотя, за исключением копирования-вставки или ссылок, я не рекомендую, чтобы you часто их использовал.: -)
Тем не менее, важно знать, что именно так Git делает это, потому что почти в каждом коммите также указан необработанный хэш-идентификатор хотя бы одного родительского коммита.Родителем является коммит, который приходит до этого коммита.Единственный коммит, который , а не для перечисления родителя, является самым первым коммитом, когда-либо сделанным , который, конечно, не имеет предыдущего коммита.
Что все это означаетчто, начиная с последнего коммита в любой ветви, Git может работать в обратном направлении до предыдущих коммитов.Затем у Git есть таблица, которая переходит от читаемых человеком имен веток, таких как master
, к необработанным хэш-идентификаторам коммитов.Чтобы добавить новый коммит в ветку:
- Git получает текущий идентификатор хэша коммита из имени ветви.
- Git Putэто в новый коммит, как родитель нового коммита.Git также добавляет новый снимок, конечно же, ваше имя и так далее.Это создает новый, уникальный, большой уродливый хеш-идентификатор.
- Затем Git записывает новый хеш-идентификатор коммита в имя
master
.
Когда что-тосодержит идентификатор хеша коммита, мы говорим, что указывает на коммит.Таким образом, имя ветки всегда указывает на последний коммит в ветке.Последний коммит указывает на его предшественника, который указывает на другой шаг и так далее, так что Git может работать в обратном направлении.Этот шаг назад, один коммит за раз, - это то, как Git хранит историю всего, что вы - и все остальные - когда-либо делали: каждый коммит записывает моментальный снимок во времени, и каждый коммит (кроме первого) имеет «ранее, вещи»выглядело как ... "историческая ссылка.
Рисование графика
Когда вы создаете новую ветвь, Git создает новую запись в таблице, так что обе ветви указывают на одинаковую совершать.В целях рисования позвольте мне использовать заглавные буквы для каждого коммита вместо большого уродливого хеш-идентификатора.Тогда у нас может быть следующее:
... <-F <-G <-H <-- master, develop (HEAD)
Как только коммит сделан, ничто внутри него не может измениться.Так что H
всегда указывает на G
и так далее.(Имена ветвей могут и всегда изменяться, обычно для размещения новых коммитов.) Итак, еще раз, для целей рисования в тексте StackOverflow я опущу внутренние стрелки и оставлю только стрелки с названиями ветвей:
...--F--G--H <-- master, develop (HEAD)
ИмяHEAD
прикреплено к одному из названий веток.Вот как Git знает , какую ветвь мы используем , потому что когда мы делаем новый коммит, Git должен обновить это имя ветви.Давайте сделаем один новый коммит сейчас и назовем его хеш I
:
...--F--G--H <-- master
\
I <-- develop (HEAD)
Теперь давайте вернемся к master
и сделаем там новый коммит, который мы можем назвать J
:
J <-- master (HEAD)
/
...--F--G--H
\
I <-- develop
Обратите внимание, что коммиты H
и более ранние находятся на обеих ветвях.
Слияние как глагол
Если мы теперь попросим Git объединить develop
(т. е. commit I
) обратно в master
(т. е. commit J
), Git найдет лучший общий коммит предка .Грубо говоря, это первый общий коммит, который Git может найти, работая в обратном направлении из обеих веток.В данном случае это вполне очевидно, что коммит H
.
Найдя коммит общего предка - который Git называет базой слияния - теперь Git должен выяснить, что мы изменилось, и что они изменились.То есть Git должен diff
commit H
, основа слияния, против двух кончиков ветвей: --ours
, который является commit J
, и --theirs
, который является commit I
.На самом деле для этого нужны две отдельные команды git diff
:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed
git diff --find-renames <hash-of-H> <hash-of-I> # what they changed
Git теперь может объединить эти два набора изменений.Мы коснулись некоторых файлов, и они коснулись некоторых файлов.Когда мы и они коснулись одинаковых файлов, мы изменили некоторые строки из базы слияния на наш коммит, и они изменили некоторые строки из базы слияния на их коммит.
Конфликт возникает, если Git решает, что мы прикоснулись к одинаковым строкам, но сделал различные изменения в этих строках. То же самое здесь в основном очевидно - если я изменил строку 17, а они изменили строку 17, мы, очевидно, изменили те же строки.Тем не менее, same может быть немного странным: например, если мы оба добавили разный текст в end файла, Git не знает, в какой порядок их помещать, поэтомуэто также конфликт.
Но пока мы касались разных строк или разных файлов , конфликта нет: Git применяет обоих наборы изменений в каждом файле слияния, чтобы получить объединенный результат.Затем Git может сделать новый коммит из объединенных изменений.
Процесс объединения называется трехсторонним объединением . См. Также Почему трехстороннее слияние выгоднее двухстороннего слияния?
Слияние как прилагательное или существительное
Этот новый коммит имеет своеобразное свойство;давайте покажем это, нарисовав результат:
J
/ \
...--F--G--H K <-- master (HEAD)
\ /
I <-- develop
Здесь произошло следующее: новый коммит K
запоминает два предыдущих коммитов: во-первых, его собственный предыдущий коммит был J
.Это K
первый родитель.Но у K
есть и второй родитель: он также помнит, что он произошел от I
.Это позволяет Git выполнять намного меньше работы при будущем слиянии, поскольку оно меняет то, какой коммит будет базой слияния позже .(Это самая сложная часть теории графов, которую мы просто пропустим. ?)
Процесс объединения изменений и создания нового коммита - это форма глагола слова merge , т. Е. для объединения .Фиксация, сделанная на , этот процесс использует то же слово, что и прилагательное: K
- это коммит слияния .Пользователи Git и Git часто сокращают его, говоря, что K
- это слияние , в котором слово merge используется как существительное.
A коммит слияния - это просто любой коммит как минимум с двумя родителями .Команда git merge
часто делает такие коммиты.(Но не всегда! Это еще одна часть крутой кривой обучения Git. А пока давайте просто проигнорируем это, потому что GitHub пытается - полу-успешно - скрыть это от вас.) В конце концов, когда выиметь слияние (существительное), оно было сделано в процессе для слияния (глагол). В процессе используются три входа - база слияния и две подсказки ветвления - дляпроизвести объединенные изменения.Затем команда git merge
дает результат в форме существительного.
Очень важно grok этот процесс слияния , который в основном просто требует времени и практики.Причина в том, что не только git merge
выполняет слияние как действие глагола : многие другие команды Git также делают это, они просто не делают коммитов слияния , когда онисделано.Каждый раз, когда вы запускаете git rebase
, git cherry-pick
или git revert
, вы будете использовать механизм слияния Git.Даже кажущийся простым (обманчивым, я думаю) git stash
использует механизм слияния Git.Когда Git делает это правильно сам по себе - что чаще всего - вам не нужно думать об этом, но когда он идет не так, вам нужно знать, что делать.
(С Git,Я считаю также полезным установить для параметра конфигурации merge.conflictStyle
значение diff3
, поэтому, когда Git оставляет запутанный беспорядок конфликтующего слияния, чтобы вы могли его исправить, он показывает входную базу слияния вместе сдве подсказки о ветвях. Другие люди предпочитают вместо этого использовать причудливые инструменты слияния на основе окон, так что это дело вкуса.)
На GitHub pull-запросы
Теперь, когда у нас естьвыше в качестве фона, давайте посмотрим, как на самом деле работает кнопка GitHub «сделать запрос на получение».Чтобы сообщить вам, может ли ваша ветвь быть объединена с чьей-либо веткой, GitHub на самом деле делает слияние как тест-слияние. 1 Этот тест объединяет эту GitHubdo не находится на какой-либо ветви вообще, но все равно проходит через ту же общую идею.Если нет конфликтов слияния, тест завершается успешно, и GitHub сообщает, что способен объединить .Если есть конфликтов, GitHub выбрасывает тестовое слияние - оно не может завершить его больше, чем сам Git - и сообщает вам, что существуют конфликты.
Если есть конфликты, конечно, вам решать, что с этим делать.Теперь вам нужно понять описанный выше трехсторонний процесс слияния, который требует понимания графа - или, если не совсем, понимания всей теории графов, по крайней мере, имея хорошее представление о том, что такое база слиянияявляется.Вы можете найти базу, сравнить ее с двумя советами и посмотреть, как возник конфликт.(Или установите merge.conflictStyle
на diff3
, и Git оставит источник конфликта в рабочем дереве, где вы можете редактировать его напрямую.)
1 Я пропускаю некоторые важные аспекты того, как Git передает коммитов из одного хранилища в другое здесь.GitHub, как я уже упоминал, имеет и ваш , и свой репозиторий на одном и том же веб-сайте, поэтому они могут и обманывают здесь - им не нужно ничего передавать, ониесть все это.Точно так же они на самом деле не запускают git merge
, потому что для этого требуется рабочее дерево: все репозитории на GitHub - это так называемые bare репозитории, без рабочих деревьев.Но все это мелкие детали, которые портят приятную обзорную идею «GitHub выполняет тестовое слияние»: они do фактически запускают тестовое слияние.У них просто есть много хитрых битов, которые нужно использовать для достижения этого.
В этой сноске также стоит упомянуть, что GitHub делает здесь эквивалент git merge --no-ff
.Невозможно сделать эквивалент git merge --ff-only
или git merge
без какой-либо ручки управления ускоренной перемоткой вперед.
Для получения тягиst N , сам по себе запрашиваемый коммит можно извлечь из ссылки refs/pull/<em>N</em>/head
.Если тестовое слияние выполнено успешно, оно находится под ссылкой refs/pull/<em>N</em>/merge
.